[学习笔记]Lucas定理和Kummer定理

写这篇笔记主要是因为被一道一试题坑了……
然后去请教了数论带师 wky,受益匪浅。
比较简单,不常用但又蛮有用的东西,记录一下。
为了与数学课里的常见写法保持一致,组合数记为 \(\text{C}_n^m\) 而非 \(\binom{n}{m}\)

Lucas 定理

中文音译:卢卡斯。

\(p\) 为质数,则:

\[\text{C}_n^m\equiv \text{C}_{[n/p]}^{[m/p]}\times\text{C}_{n\bmod p}^{m\bmod p} \pmod p \]

其中 \([x]\) 为高斯函数,表示下取整,其实就是整除。

证明略,OI 中必备基础数论定理。

一个有用的推论

本质是 Lucas 定理的连续应用。

\(\text{C}_n^m \bmod p\) 中的 \(n\)\(m\) 表示成 \(p\) 进制数:

\[n=\sum\limits_{i=0}^k a_i\cdot p^i \left(a_k \ne 0 \right) \]

\[m=\sum\limits_{i=0}^k b_i\cdot p^i \]

\[\text{C}_n^m \equiv \prod\limits_{i=0}^k\text{C}_{a_i}^{b_i} \pmod p \]

Kummer 定理

中文音译:库默尔。

前置芝士:设 \(v_p(n)\) 表示 \(n\) 的质因数分解中质数 \(p\) 的幂次,则:

\[v_p(n!)=\sum\limits_{i=1}^\infty\left[\dfrac{n}{p^i}\right] \]

推论:

\[\begin{aligned} v_p(\text{C}_{n+m}^m)&=v_p((n+m)!)-v_p(m!)-v_p(n!) \\ &=\sum\limits_{i=1}^\infty\left[\dfrac{n+m}{p^i}\right]-\sum\limits_{i=1}^\infty\left[\dfrac{m}{p^i}\right]-\sum\limits_{i=1}^\infty\left[\dfrac{n}{p^i}\right] \\ &=\sum\limits_{i=1}^\infty\left(\left[\dfrac{n+m}{p^i}\right]-\left[\dfrac{m}{p^i}\right]-\left[\dfrac{n}{p^i}\right]\right) \\ \end{aligned}\]

\(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;
}
posted @ 2022-08-11 23:36  Andrewzdm  阅读(558)  评论(0编辑  收藏  举报