作者: KSkun

Lucas定理及其扩展原理及应用

Lucas定理及其扩展原理及应用

Lucas定理(Lucas’s theorem)

内容

对于非负整数m和n和素数p,有
\mathrm{C}_n^m \equiv \prod_{i=0}^k \mathrm{C}_{n_i}^{m_i} \pmod{p}
其中,m = m_kp^k + m_{k-1}p^{k-1} + \cdots + m_1p + m_0n = n_kp^k + n_{k-1}p^{k-1} + \cdots + n_1p + n_0
也就是说,这个是m和n的p进制展开。
m < n时,\mathrm{C}_n^m = 0

求组合数的值

既然我们可以把m和n以p进制展开求值,那么我们可以设计递归来解决这个问题。
我们知道\mathrm{C}_n^0 = 1,那么以m=0为递归终点即可。代码如下。

inline LL lucas(LL n, LL m, LL p) {
    if(!m) return 1;
    return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}

其中C就是直接求组合数值的函数,这个可以预处理杨辉三角或者预处理阶乘及阶乘逆元。

例题:【P3807】【模板】卢卡斯定理 – 洛谷

直接应用上述方法即可。代码如下。

// Code by KSkun, 2018/3
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005;

LL T, n, m, p, inv[MAXN], mul[MAXN];

inline LL C(LL n, LL m, LL p) {
    if(m > n) return 0;
    return mul[n] * inv[n - m] % p * inv[m] % p;
}

inline LL lucas(LL n, LL m, LL p) {
    if(!m) return 1;
    return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}

inline void init() {
    mul[0] = inv[0] = mul[1] = inv[1] = 1;
    for(int i = 2; i <= std::min(n, p); i++) {
        inv[i] = -(p / i) * inv[p % i] % p;
    }
    for(int i = 2; i <= std::min(n, p); i++) {
        inv[i] = inv[i] * inv[i - 1] % p;
        mul[i] = mul[i - 1] * i % p;
    }
}

int main() {
    T = readint();
    while(T--) {
        n = readint(); m = readint(); p = readint(); n += m;
        init();
        printf("%lld\n", (lucas(n, m, p) + p) % p);
    }
    return 0;
}

扩展Lucas定理(exLucas)

内容

扩展Lucas定理与Lucas定理的区别是模数非质数,此时,我们考虑将模数质因数分解如下。
P = p_1^{k_1} \cdot p_2^{k_2} \cdots p_n^{k_n}
只要我们能求出组合数对于每个p_i^{k_i}的余数,我们就可以利用中国剩余定理(中国剩余定理及其扩展原理及应用 | KSkun’s Blog)解决这个问题。也就是说,我们将问题变为了这个:
\begin{cases} \mathrm{C}_n^m \equiv a_1 \pmod{p_1^{k_1}} \\ \mathrm{C}_n^m \equiv a_2 \pmod{p_2^{k_2}} \\ \vdots \\ \mathrm{C}_n^m \equiv a_n \pmod{p_n^{k_n}} \end{cases}
现在的问题是,上面每个式子的余数怎么求出。
如果我们使用组合数的定义,我们可以分别求出n! \bmod p_i^{k_i}, m! \bmod p_i^{k_i}, (n-m)! \bmod p_i^{k_i}
首先,我们知道阶乘中存在一些p_i因子,对于这些因子我们可以处理出来单独运算。对于提取一个因子后的阶乘,我们观察它们的规律。假如p=3,要求10!,提取后就变成了:
1 \times 2 \times 1 \times 4 \times 5 \times 2 \times 7 \times 8 \times 3 \times 10 \times 3^3
p_i的倍数组成了一个新的阶乘,这一部分我们递归下去处理。考虑剩下的部分,即1 \times 2 \times \cdots \times (p_i^{k_i}-1) \times (p_i^{k_i}+1) \times \cdots \times (2p_i^{k_i}-1) \times \cdots。我们发现\prod_{i=1, i \bmod p_i \neq 0}^{p_i^{k_i}-1} i \equiv \prod_{i=p_i^{k_i}+1, i \bmod p_i \neq 0}^{2p_i^{k_i}-1} i \pmod{p_i^{k_i}},也就是说,只需要求一个长为p_i^{k_i}的阶乘,并且计算有多少完整的一段,使用快速幂即可求出。至于多出去的那段,直接暴力乘进去即可。

例题:[国家集训队]礼物 题解 | KSkun’s Blog

代码参见该题题解文章。代码内有注释,可以对照上述内容理解。

参考资料

[SDOI2008]Sandy的卡片 题解

[SDOI2008]Sandy的卡片 题解

题目地址:洛谷:【P2463】[SDOI2008]Sandy的卡片 – 洛谷、BZOJ:Problem 4698. — Sdoi2008 Sandy的卡片

题目描述

Sandy和Sue的热衷于收集干脆面中的卡片。
然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积攒卡片兑换超炫的人物模型。
每一张卡片都由一些数字进行标记,第i张卡片的序列长度为Mi,要想兑换人物模型,首先必须要集够N张卡片,对于这N张卡片,如果他们都有一个相同的子串长度为k,则可以兑换一个等级为k的人物模型。相同的定义为:两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串。
Sandy的卡片数远远小于要求的N,于是Sue决定在Sandy的生日将自己的卡片送给Sandy,在Sue的帮助下,Sandy终于集够了N张卡片,但是,Sandy并不清楚他可以兑换到哪个等级的人物模型,现在,请你帮助Sandy和Sue,看看他们最高能够得到哪个等级的人物模型。

输入输出格式

输入格式:
第一行为一个数N,表示可以兑换人物模型最少需要的卡片数,即Sandy现在有的卡片数
第i+1行到第i+N行每行第一个数为第i张卡片序列的长度Mi,之后j+1到j+1+Mi个数,用空格分隔,分别表示序列中的第j个数

输出格式:
一个数k,表示可以获得的最高等级。

输入输出样例

输入样例#1:

2
2 1 2
3 4 5 9

输出样例#1:

2

说明

数据范围:
30%的数据保证n<=50
100%的数据保证n<=1000,M<=1000,2<=Mi<=101

题解

找最长公共子串啊,DP?显然不是DP,那是两个字符串。
我们考虑用字符串匹配算法来做,选定第一个串为模式串,来匹配剩下的串。
加上一个数变成另一个串怎么做?差分掉。
子串怎么做?枚举子串,分别匹配。
等一下,现在的算法好像是O(len^2 \cdot \sum len)的耶,好像不太可做。
我们只枚举子串的起点,然后在KMP过程中记录下最长匹配长度即可。
最后的时间复杂度是O(len \cdot \sum len),好像可做。

代码

// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>

#include <algorithm>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 1005, INF = 1e9;

int n, t, fail[MAXN], ans;
std::vector<int> str[MAXN];

inline void calfail(int x) {
    memset(fail, 0, sizeof(fail));
    int i = 0, j = -1;
    fail[0] = -1;
    for(; i < str[x].size(); i++, j++) {
        while(j >= 0 && str[x][j] != str[x][i]) {
            j = fail[j];
        }
        fail[i + 1] = str[x][j + 1] == str[x][i + 1] ? fail[j + 1] : j + 1;
    }
}

inline int match(int t, int p) {
    int i = 0, j = 0, cnt = -INF;
    for(; i < str[t].size(); i++, j++) {
        while(j >= 0 && str[p][j] != str[t][i]) {
            j = fail[j];
        }
        cnt = std::max(cnt, j);
    }
    return cnt + 2;
}

int main() {
    n = readint();
    for(int i = 0; i < n; i++) {
        t = readint();
        for(int j = 0; j < t; j++) {
            str[i].push_back(readint());
        }
        for(int j = 0; j < t - 1; j++) {
            str[i][j] = str[i][j + 1] - str[i][j];
        }
        str[i].pop_back();
    }
    for(int i = 0; !str[0].empty(); i++) {
        calfail(0);
        int tmp = INF;
        for(int j = 1; j < n; j++) {
            tmp = std::min(tmp, match(j, 0));
        }
        str[0].erase(str[0].begin());
        ans = std::max(ans, tmp);
    }
    printf("%d", ans);
    return 0;
}
[SDOI2010]外星千足虫 题解

[SDOI2010]外星千足虫 题解

题目地址:洛谷:【P2447】[SDOI2010]外星千足虫 – 洛谷、BZOJ:Problem 1923. — [Sdoi2010]外星千足虫

题目描述

公元2089年6月4日,在经历了17年零3个月的漫长旅行后,“格纳格鲁一号”载人火箭返回舱终于安全着陆。此枚火箭由美国国家航空航天局(NASA)研制发射,行经火星、金星、土卫六、木卫二、谷神星、“张衡星”等23颗太阳系星球,并最终在小行星“杰森星”探寻到了地外生命。宇航员在“杰森星”地表岩层下45.70米位置发现一批珍贵的活体生命样本,并将其带回检测。在带回的活体样本中,最吸引人的当属这些来自外星的千足虫了。这些虫子身躯纤长,身体分为若干节。受到触碰时,会将身体卷曲成圆环形,间隔一段时间后才会复原活动。
有趣的还不止如此。研究人员发现,这些虫子的足并不像地球千足虫成对出现、总共偶数条——它们每节身体下方都有着不定数量的足,但足的总数一定是奇数条!虽然从外观难以区分二者,但通过统计足的数目,科学家们就能根据奇偶性判断出千足虫所属的星球。
作为J国派去NASA的秘密间谍,你希望参加这次研究活动以掌握进一步的情报,而NASA选拔的研究人员都是最优秀的科学家。于是NASA局长Charles Bolden出了一道难题来检测你的实力:
现在你面前摆有1…N编号的N只千足虫,你的任务是鉴定每只虫子所属的星球,但不允许亲自去数它们的足。Charles每次会在这N只千足虫中选定若干只放入“昆虫点足机”(the Insect Feet Counter, IFC)中,“点足机”会自动统计出其内所有昆虫足数之和。Charles会将这个和数mod 2的结果反馈给你,同时告诉你一开始放入机器中的是哪几只虫子。他的这种统计操作总共进行M次,而你应当尽早得出鉴定结果。
假如在第K次统计结束后,现有数据就足以确定每只虫子的身份,你就还应将这个K反馈给Charles,此时若K<M,则表明那后M-K次统计并非必须的。
如果根据所有M次统计数据还是无法确定每只虫子身份,你也要跟Charles讲明:就目前数据会存在多个解。

输入输出格式

输入格式:
输入文件insect.in第一行是两个正整数N, M。
接下来M行,按顺序给出Charles这M次使用“点足机”的统计结果。每行包含一个“01”串和一个数字,用一个空格隔开。“01”串按位依次表示每只虫子是否被放入机器:如果第i个字符是“0”则代表编号为i的虫子未被放入,“1”则代表已被放入。后面跟的数字是统计的昆虫足数mod 2的结果。
由于NASA的实验机器精确无误,保证前后数据不会自相矛盾。即给定数据一定有解。

输出格式:
输出文件insect.out在给定数据存在唯一解时有N+1行,第一行输出一个不超过M的正整数K,表明在第K次统计结束后就可以确定唯一解;接下来N行依次回答每只千足虫的身份,若是奇数条足则输出“?y7M#”(火星文),偶数条足输出“Earth”。如果输入数据存在多解,输出“Cannot Determine”。
所有输出均不含引号,输出时请注意大小写。

输入输出样例

输入样例#1:

3 5
011 1
110 1
101 0
111 1
010 1

输出样例#1:

4
Earth
?y7M#
Earth

输入样例#2:

5 7
01100 1
11000 1
10100 0
11100 1
00011 1
00000 0
11111 0

输出样例#2:

Cannot Determine

说明

对于每一个测试点,如果你的输出文件与答案文件完全相同,该测试点得满分;
否则,对于存在唯一解的测试点,如果你正确回答所有千足虫的身份,将得到50%的分数;
其他情况,该测试点得零分。
【数据规模和约定】
对于20%的数据,满足N=M≤20;
对于40%的数据,满足N=M≤500;
对于70%的数据,满足N≤500,M≤1,000;
对于100%的数据,满足N≤1,000,M≤2,000。

题解

本题实际上给了m组线性方程,要求在\bmod 2意义下的解。我们依然可以套用高斯消元。但是我们观察到数据范围不允许O(n^3)算法,由于方程在\bmod 2意义下,可以用bitset压起来算,这样高斯消元就变为O(n^3 / 64)算法了。
具体做法与高斯消元并无差别,只是替换成位运算了。
对于到第几个方程能够知道结果,只需要在找行主元的时候记下行主元的行号最大值。

代码

// Code by KSkun, 2018/3
#include <cstdio>

#include <algorithm>
#include <bitset>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline bool is01(char c) {
    return c == '0' || c == '1';
}

inline int read01() {
    char c;
    while(!is01(c = fgc()));
    return c - '0';
}

const int MAXN = 2005;

int n, m, t, ans;
bool fail;
std::bitset<MAXN> mat[MAXN];

inline void gauss() {
    for(int i = 1; i <= n; i++) {
        int r = i;
        for(int j = i; j <= m; j++) {
            if(mat[j][i]) {
                r = j;
                break;
            }
        }
        ans = std::max(ans, r);
        if(r != i) {
            std::swap(mat[i], mat[r]);
        }
        if(!mat[i][i]) {
            fail = true;
            return;
        }
        for(int j = 1; j <= m; j++) {
            if(j != i && mat[j][i]) {
                mat[j] ^= mat[i];
            }
        }
    }
} 

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= n + 1; j++) {
            mat[i][j] = read01();
        }
    }
    gauss();
    if(fail) {
        puts("Cannot Determine");
    } else {
        printf("%d\n", ans);
        for(int i = 1; i <= n; i++) {
            printf(mat[i][n + 1] ? "?y7M#\n" : "Earth\n");
        }
    }
    return 0;
}