[清华集训2016] 组合数问题
实际上是要问有多少对 \(i, j(j \le i)\) 满足 \(\dbinom{i}{j} \equiv 0 \pmod{P}\),大组合数取模问题可以考虑卢卡斯定理这个有利的武器。之前一直不会证明卢卡斯定理,今天学会了证法简单记录一下。
一个引理:\((x + 1) ^ P \equiv x ^ P + 1 \pmod{P}\) 其中 \(P\) 为质数。
考虑使用二项式定理展开 \((x + 1) ^ P = \sum\limits_{k = 0} ^ P \dbinom{P}{k} x ^ k\),对于 \(k = 1, 2, \cdots P - 1\) 都有 \(\dbinom{P}{k} = \dfrac{P!}{k! (P - k)!}\)。因为 \(P\) 为质数,因此 \(\gcd(P, i) = 1(1 \le i \le P - 1)\),故 \(\dfrac{P!}{k! (P - k)!}\) 中 \(P\) 不会被约掉,所以 \(\dbinom{P}{k}(1 \le k \le P - 1)\) 一定包含 \(P\) 这个约数,所以 \((x + 1) ^ P \equiv \sum\limits_{k = 0} ^ P \dbinom{P}{k} x ^ k \equiv x ^ P + 1 \pmod{P}\)。
再来观察卢卡斯定理的恒等式:\(\dbinom{n}{m} \equiv \dbinom{n / P}{m / P} \times \dbinom{n \% P}{m \% P}\) 其中 \(P\) 为质数,\(a / b = \lfloor \dfrac{a}{b} \rfloor\)。
证明这种恒等式的一个重要的方法是将左右两边看作两个多项式的系数再利用多项式恒等原理来证明系数恒等。多项式恒等原理指的是两个 \(n\) 次多项式 \(A(x), B(x)\) 两个多项式横相等那么 \(A(x)\) 和 \(B(x)\) 中的每个系数都相等。因为两个多项式横相等,那么我们找到 \(n + 1\) 个点值还原出的多项式也应该相同,即两个多项式的系数均相同。
首先我们令 \(q_n = \lfloor \dfrac{n}{P} \rfloor, r_n = n \% P\),注意到 \(\dbinom{n}{m}\) 是二项式定理中 \(x ^ m\) 的系数,于是我们想办法将二项式定理的系数化成 \(\dbinom{n / P}{m / P} \times \dbinom{n \% P}{m \% P}\) 的形式。于是可以有如下推导:
可以发现 \(pi + j\) 实际上对应的是 \(m \le n\) 的所有数 \(m\) 并且一一对应,于是上面的式子可以继续改写为:
根据多项式恒等原理我们就有 \(\dbinom{n}{m} \equiv \dbinom{n / P}{m / P} \times \dbinom{n \% P}{m \% P}\)。
回到本题当中,假设 \(n_i, m_i\) 分别表示 \(n, m\) 在 \(P\) 进制下第 \(i\) 位的值,那么我们有 \(\dbinom{n}{m} = \prod\limits \dbinom{n_i}{m_i}\) 又因为 \(P\) 为质数,因此 \(P\) 不能被表示成两个小于它的数相乘,因此 \(\dbinom{n}{m} \equiv 0 \pmod{P}\) 当且仅当存在一位 \(n_i, m_i\) 满足 \(\dbinom{n_i}{m_i} \equiv 0 \pmod{P}\) 即 \(m_i > n_i\)。于是我们就可以考虑开始计数了,令 \(dp_{i, f, lnm, ln, lm}\) 表示当前考虑到第 \(i\) 位,是否已经存在某一位满足 \(m_i > n_i\),当前选择的每一位两个数选择的是否都相同(因为不能出现第二个数选择得比第一个数大的情况),第一个数是否选到上界,第二个数是否选到上界。转移只需要枚举当前这个位置两个数分别选什么即可,使用记忆化搜索实现。
#include<bits/stdc++.h>
using namespace std;
#define N 60 + 5
#define Mod 1000000007
#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++i)
int T, P, n, m, x, y, len, cnt1, cnt2, a[N], b[N], dp[N][2][2][2][2];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b){
return (a += b) >= Mod ? a - Mod : a;
}
int dfs(int p, bool f, bool lnm, bool ln, bool lm){
if(p > len) return f ? 1 : 0;
if(dp[p][f][lnm][ln][lm] != -1) return dp[p][f][lnm][ln][lm];
int upn = (ln ? a[p] : P - 1), upm = (lm ? b[p] : P - 1), ans = 0;
rep(i, 0, upn) rep(j, 0, upm){
if(lnm && i < j) continue;
ans = Inc(ans, dfs(p + 1, (f | (i < j)), (lnm & (i == j)), (ln & (i == upn)), (lm & (j == upm))));
}
return dp[p][f][lnm][ln][lm] = ans;
}
signed main(){
T = read(), P = read();
while(T--){
n = x = read(), m = y = read();
cnt1 = cnt2 = 0;
memset(a, 0, sizeof(a)), memset(b, 0, sizeof(b)), memset(dp, -1, sizeof(dp));
while(x) a[++cnt1] = x % P, x /= P;
while(y) b[++cnt2] = y % P, y /= P;
len = max(cnt1, cnt2);
reverse(a + 1, a + len + 1), reverse(b + 1, b + len + 1);
printf("%lld\n", dfs(1, 0, 1, 1, 1));
}
return 0;
}