[清華集訓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)!}\)
再來觀察盧卡斯定理的恆等式:\(\dbinom{n}{m} \equiv \dbinom{n / P}{m / P} \times \dbinom{n \% P}{m \% P}\)
證明這種恆等式的一個重要的方法是將左右兩邊看作兩個多項式的係數再利用多項式恆等原理來證明係數恆等。多項式恆等原理指的是兩個 \(n\) 次多項式 \(A(x), B(x)\) 兩個多項式橫相等那麼 \(A(x)\) 和 \(B(x)\) 中的每個係數都相等。因為兩個多項式橫相等,那麼我們找到 \(n + 1\) 個點值還原出的多項式也應該相同,即兩個多項式的係數均相同。
首先我們令 \(q_n = \lfloor \dfrac{n}{P} \rfloor, r_n = n \% P\)
\[\begin{aligned} (x + 1) ^ n &\equiv (x + 1) ^ {q_n \times P + r_n}\\ &\equiv ((x + 1) ^ P) ^ {q_n} \times (x + 1) ^ {r_n}\\ &\equiv (x ^ P + 1) ^ {q_n} \times (x + 1) ^ {r_n}\\ &\equiv \sum\limits_{i = 0} ^ {q_n} \dbinom{q_n}{i} x ^ {pi} \sum\limits_{j = 0} ^ {r_n} \dbinom{r_n}{j} x ^ j \pmod{P} \end{aligned} \]
可以發現 \(pi + j\) 實際上對應的是 \(m \le n\) 的所有數 \(m\) 並且一一對應,於是上面的式子可以繼續改寫為:
\[\begin{aligned} \sum\limits_{m = 0} ^ n \dbinom{n / P}{m / P} \times \dbinom{n \% P}{m \% P} x ^ m &\equiv (x + 1) ^ n\\ &\equiv \sum\limits_{m = 0} ^ n \dbinom{n}{m} x ^ m \pmod{P} \end{aligned} \]
根據多項式恆等原理我們就有 \(\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;
}