[学习笔记]Lucas定理和Kummer定理
写这篇笔记主要是因为被一道一试题坑了……
然后去请教了数论带师 wky,受益匪浅。
比较简单,不常用但又蛮有用的东西,记录一下。
为了与数学课里的常见写法保持一致,组合数记为 \(\text{C}_n^m\) 而非 \(\binom{n}{m}\)。
Lucas 定理
中文音译:卢卡斯。
\(p\) 为质数,则:
其中 \([x]\) 为高斯函数,表示下取整,其实就是整除。
证明略,OI 中必备基础数论定理。
一个有用的推论
本质是 Lucas 定理的连续应用。
将 \(\text{C}_n^m \bmod p\) 中的 \(n\) 和 \(m\) 表示成 \(p\) 进制数:
则
Kummer 定理
中文音译:库默尔。
前置芝士:设 \(v_p(n)\) 表示 \(n\) 的质因数分解中质数 \(p\) 的幂次,则:
推论:
将 \(n\) 和 \(m\) 化成 \(p\) 进制数,进行加法。当没有进位时,显然上式为 \(0\)。
而对一个给定的 \(i\),\(\left[\frac{n+m}{p^i}\right]-\left[\frac{m}{p^i}\right]-\left[\frac{n}{p^i}\right]=1\) 的充要条件是 \(m\) 和 \(n\) 在 \(p\) 进制表示的加法中在这一位上进位。
定理:\(v_p(\text{C}_{n+m}^m)\) 等于 \(n\) 和 \(m\) 在 \(p\) 进制加法中进位的次数。
应用
某道数学题
先放上把自己坑到的数学题:
\(\qquad (x-1)^{2017}\) 展开式中,系数为偶数的有多少项。(填空)
由二项式定理,就是求有多少个 \(\text{C}_{2017}^x \equiv 0 \pmod 2\)
那么正难则反,求 \(\text{C}_{2017}^x \equiv 1 \pmod 2\) 的个数。
如果 \(2017\) 某位上二进制为 \(0\),那么 \(x\) 这位上二进制必须为 \(0\);如果 \(2017\) 某位上二进制为 \(1\),那么 \(x\) 这位上二进制可为 \(0\) 可为 \(1\)。
简要过程如下:
- \((2017)_{10}=(11111100001)_2\)
- \((11111100001)_2\) 里有 \(7\) 个 \(1\)
- \(2^7=128\)
- \(\text{ans} = 2018-128=1890\)
- 注意到常数项为 \(-1\),不会被我们计算在偶数内,所以不管常数项算不算系数都不影响答案。
CF582D Number of Binominal Coefficients
Kummer 定理模板题。
使用数位DP统计进位若干次的数对个数。
各种转移的情况自己推下式子计算系数就好。
推式子写代码三小时,CF582D 治好了我的精神内耗。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxl = 3505; //2^3505>10^1000
const ll mod = 1e9 + 7;
char s[maxl];
ll b[maxl];
ll p, alpha, n;
int len = 1;
ll dp[2][maxl][2][2]; //从高到低前i位,进位j次,是否小于alpha,后一位是否向前进位
//空间复杂度优化:滚动数组
inline void Add(ll &x, ll y) { x = (x + y) % mod; return; }
void init() { //将A化为p进制数
scanf("%lld%lld%s", &p, &alpha, s + 1);
n = strlen(s + 1);
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= len; ++j) b[j] *= 10;
b[1] += s[i] - '0';
for(int j = 1; j <= len; ++j) {
b[j + 1] += b[j] / p;
b[j] %= p;
if(b[len + 1] != 0) ++len;
}
}
reverse(b + 1, b + len + 1);
return;
}
void calc() {
dp[0][0][1][0] = 1;
//第一位取值不可进位,还必须有上界,所以只能初始化dp[0][0][1][0]为1
for(int i = 1; i <= len; ++i) { //从高到低前i位
ll x = b[i]; //上界
int c = i & 1;
for(int j = 0; j < i; ++j) dp[c][j][0][0] = dp[c][j][0][1] = dp[c][j][1][0] = dp[c][j][1][1] = 0;
for(int j = 0; j < i; ++j) { //进位j次
//coefficents
ll c1 = x * (2 * p - x - 1) / 2 % mod; //p <= a + b <= p + x - 1
ll c2 = (p - 1) * p / 2 % mod; //a + b >= p,也是 a + b < p - 1
ll c3 = x * (x + 1) / 2 % mod; //a + b < x
ll c4 = p - x - 1; //a + b = p + x
ll c5 = x + 1; //a + b = x
ll c6 = x * (2 * p - x + 1) / 2 % mod; //p - 1 <= a + b <= p + x - 2
ll c7 = p * (p + 1) / 2 % mod; //a + b >= p - 1,也是 a + b < p
ll c8 = (x - 1) * x / 2 % mod; //a + b < x - 1
ll c9 = p - x; //a + b = p + x - 1
ll c10 = x; //a + b = x - 1
if(j) Add(dp[c][j][0][0], c1 * dp[c ^ 1][j - 1][1][1] % mod);
if(j) Add(dp[c][j][0][0], c2 * dp[c ^ 1][j - 1][0][1] % mod);
Add(dp[c][j][0][0], c3 * dp[c ^ 1][j][1][0] % mod);
Add(dp[c][j][0][0], c7 * dp[c ^ 1][j][0][0] % mod);
if(j) Add(dp[c][j][1][0], c4 * dp[c ^ 1][j - 1][1][1] % mod);
Add(dp[c][j][1][0], c5 * dp[c ^ 1][j][1][0] % mod);
if(j) Add(dp[c][j][0][1], c6 * dp[c ^ 1][j - 1][1][1] % mod);
if(j) Add(dp[c][j][0][1], c7 * dp[c ^ 1][j - 1][0][1] % mod);
Add(dp[c][j][0][1], c8 * dp[c ^ 1][j][1][0] % mod);
Add(dp[c][j][0][1], c2 * dp[c ^ 1][j][0][0] % mod);
if(j) Add(dp[c][j][1][1], c9 * dp[c ^ 1][j - 1][1][1] % mod);
Add(dp[c][j][1][1], c10 * dp[c ^ 1][j][1][0] % mod);
}
}
return;
}
int main() {
init();
calc();
ll ans = 0;
for(int i = alpha; i < len; ++i) Add(ans, (dp[len & 1][i][0][0] + dp[len & 1][i][1][0]) % mod);
cout << ans << endl;
return 0;
}