(笔记)基础数论+好文收藏 二项式反演 CRT Lucas定理 容斥原理等
好文
正文
本文仅记录部分对笔者较为重要(且较为简单)的证明。
裴蜀定理
求证:\(\forall a,b,x,y,d\in\Z,a,b,d\neq 0,ax+by=d\) 有解的充分必要条件为 \(\gcd(a,b)|d\)。
考虑证明必要性,如果存在一组 \((x,y)\) 使得 \(ax+by=\gcd(a,b)\),那么其他可行解都可以通过 \((xr,yr)(r=\frac{d}{\gcd(a,b)})\) 得到,而弱命题可以通过扩展欧几里得证得。
考虑证明充分性,即 \(ax+by=d\) 定有 \(\gcd(a,b)|d\),因为 \(a|\gcd(a,b),b|\gcd(a,b)\),所以左式 \(d|\gcd(a,b)\)。
扩展欧几里得
求证:存在 \(ax+by=\gcd(a,b)\) 的整数二元组 \((x,y)\) 可以被 \(bx_0+(a\bmod b)y_0=\gcd(a\bmod b,b)\) 的整数二元组 \((x_0,y_0)\) 推出。
首先考虑证明辗转相除法求 \(\gcd\) 的正确性,若 \(a>b,\forall d|a,d|b\),那么定有 \(d|(a-b)\),即对于 \(a,b\) 的所有公因数都是 \(a-b,b\) 的公因数,扩展即可证明辗转相除正确性,就有 \(\gcd(a,b)=\gcd(a\bmod b,b)\)。
Proof1(方便记忆)
注意到 \(a,b\) 都是已知量,那么我们只需要求出上述方程的解,即可以变成:
的形式,然后有:
从而,原式的解就是:
Proof2(一样的只是换元)
不妨让 \(a=mb+r,m=\lfloor\frac{a}{b}\rfloor,r=a\bmod b,d=\gcd(a,b)\) 那么可以由条件得到 \(bx_0+ry_0=d①\) 的一组整数二元组解 \((x_0,y_0)\)。
变形 \(ax+by=d\) 得到
观察到当 \(b,r\) 均确定时,①式与②式有类似同构的关系,由此可以得到:
进而,
由于 \(y_0,x_0,m\) 都是整数,所以得到的 \(x,y\) 也是整数。
这样 \((a,b,d)\) 的问题就可以由 \((a\bmod b,b,d)\) 的子问题解决(类似数学归纳法),然后由辗转相除法一定会到达一个边界 \((d,0,d)\),这时候令 \(x_0=1,y_0=0\) 就可回溯求出上面原方程的解。
二元一次不定方程求任意整数解
利用 exgcd 先求出一组特殊解 \(ax+by=d\) 的 \((x_0,y_0)\),然后所有解都可以表示为 \((x_0+t\frac{\text{lcm}(a,b)}{a},y_0-t\frac{\text{lcm}(a,b)}{b})\),容易观察到两边增减恰好互相抵消,所以这样做是对的。
线性递推求逆元
已知模数 \(p\),求 \(inv[i]\equiv i^{-1}\pmod p\)
首先有\(1^{-1}\equiv 1\pmod p\)
然后设 \(p=ki+r,(1<r<i<p)\),也就是 \(k=\lfloor \frac{p}{i} \rfloor,r=p\bmod i\)
再放在模意义下可以得到:
乘上 \(i^{-1},r^{-1}\) 得到:
牛顿二项式定理
欧拉函数与莫比乌斯反演
定义:\(\varphi(n)\) 定义为不超过 \(n\) 的数中与 \(n\) 互质的数的个数,即 \(\varphi(n)=\sum_{i=1}^n [gcd(i,n)=1]\)。
两个函数有一些常用的性质,包括但不限于
常用的求法有线性筛或素数筛法,这里贴个 \(O(\sqrt{n})\) 求 \(\varphi(x)\) 的代码。
点击查看代码
int phi(int x){
int res=x;
for(int i=2;i*i<=x;i++){
if(x%i==0){
res=res/i*(i-1);
while(x%i==0)x/=i;
}
}
if(x!=1)res=res/x*(x-1);
return res;
}
莫比乌斯反演:
狄利克雷卷积
对于两个数论函数 \(f(n),g(n),h=f*g\)
\(h(n)=\sum_{d|n}f(d)g(\frac{n}{d})=\sum_{ab=n}f(a)g(b)\)
隔板法
这玩意为什么要出现在这里?我也不知道。
标准形式:\(m\) 个抽屉(有编号),\(n\) 个物品(无编号)不可空地放入其中的方案数为 \(\begin{pmatrix}n-1\\m-1\end{pmatrix}\)。
证明:由于物品无序,考虑用 \(m-1\) 个隔板分组,则问题转化为 \(n-1\) 个可以放隔板的地方无序地选择 \(m-1\) 个放入。
一般形式:\(m\) 个抽屉(有编号),\(n\) 个物品(无编号)可空可不空地放入其中的方案数为 \(\begin{pmatrix}n+m-1\\m-1\end{pmatrix}\)。
证明:考虑转化为标准形式,每个抽屉借一个虚拟球,这样就可以保证每个抽屉至少有 \(1\) 个球,然后标准隔板法方案数是 \(\begin{pmatrix}n+m-1\\m-1\end{pmatrix}\),在这以后我们不妨去掉每个抽屉中借来的那个虚拟球,然后可以保证这个计数是双射对应的(即原命题形式保证 \(\sum=n\) 前提下每个抽屉值域 \([0,n]\),转化后为保证 \(\sum=n+m\) 前提下每个抽屉值域 \([1,n+1]\))。
具体来说,我们标准形式的证明类似划分若干连续段的问题,由于抽屉有序但是物品无序,所以第 \(i\) 个连续段的长度就对应了第 \(i\) 个抽屉里面放了多少物品。扩展到一般形式的时候,我们也只在意连续段长度,而标准形式中连续段长度 \(\sum=n\),共 \(m\) 段,且每个连续段长度 \(\geq 1\)。唯一对应到一般形式就是连续段长度 \(\sum=n\) 且连续段长度 \(\geq 0\),这和 \(\sum=n+m\),共 \(m\) 段,且连续段长度 \(\geq 1\) 是一一对应的。
所以并不需要担心新加入的虚拟球放在原序列中的哪个位置会不会对最终答案有影响的问题,因为它只是辅助转化为一般连续段划分问题而不是直接在模型中参与分段。
Lucas 定理
Lucas 定理主要适用于模数比较小时,我们直接用 \(\begin{pmatrix}m\\n\end{pmatrix}=\frac{m!}{n!(m-n)!}\) 并预处理阶乘时容易出现一种情况,即阶乘中包含可以整除模数的数,那么整个东西出来就是 0。但同时在模意义下这些阶乘上下是可以抵消的,直接用 0 算会损失信息,所以我们采用 Lucas 定理如下:
int C(int m,int n,int MOD){
if(m<n)return 0;
if(n==0)return 1;
if(m<MOD)return fac[m]*inv(fac[n],MOD)%MOD*inv(fac[m-n],MOD)%MOD;
return C(m/MOD,n/MOD,MOD)*C(m%MOD,n%MOD,MOD)%MOD;
}
然后就完美解决了问题,不过需要注意单次时间可能达到 \(O(\log_{MOD}\max(n,m))\) 的,虽然这完全可以忽略不计。
例题
求:
令 \(p=2333\),则不妨令上式为 \(f(n,k)\):
容易发现关于 \(f\) 的时间是 \(\log_p\) 级别的,\(n<p\) 的所有情况都可以 \(O(1)\) 前缀和预处理,后面那一坨用一个 Lucas 定理暴算即可,总时间复杂度 \(O(p^2+t\log_p^2 n)\),可以活下来。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD=2333;
LL C[MOD][MOD],pre[MOD][MOD];
void init(){
for(int i=0;i<MOD;i++)
C[i][i]=C[i][0]=1;
for(int i=2;i<MOD;i++)
for(int j=1;j<i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
for(int i=0;i<MOD;i++)
for(int j=0;j<=i;j++)
pre[i][j]=(C[i][j]+(j>0?pre[i][j-1]:0))%MOD;
}
LL Lucas(LL m,LL n){
if(m<n)return 0;
if(m<MOD&&n<MOD)return C[m][n];
return Lucas(m/MOD,n/MOD)*C[m%MOD][n%MOD]%MOD;
}
LL f(LL n,LL k){
if(n<MOD)return pre[n][min(n,k)];
LL r1=f(n/MOD,(k/MOD)-1);
LL r2=pre[n%MOD][n%MOD];
LL r3=Lucas(n/MOD,k/MOD)*pre[n%MOD][min(n%MOD,k%MOD)];
return (r1*r2%MOD+r3)%MOD;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
init();int t;cin>>t;
while(t--){
LL n,k;cin>>n>>k;
cout<<f(n,k)<<'\n';
}
return 0;
}
扩展 Lucas 定理
前情提要:中国剩余定理、扩展欧几里得。
注意:本文中 \(m,n\) 按照笔者的习惯与题面相反。
扩展欧几里得用于非质数模数模意义下 \(O(\log n)\) 求取逆元。
第一步转化:非质数 \(p=\prod_{i=1}^{cnt}{p_i}^{k_i},p_i\in\mathbb P\) 做质因数分解。
然后 \(x\pmod{p}\) 就可以转化成:
由于所有 \({p_i}^{k_i}\) 互质,把所有 \(b_i\) 求出后中国剩余定理合并即可。
第二步转化:考虑求出每个 \(b_i\),问题等价于 \(\begin{pmatrix}m\\n\end{pmatrix}\pmod{{p_i}^{k_i}}\),也就是:
考虑到做 Lucas 定理时,不能直接使用阶乘是因为有可能阶乘中包含的模意义下 \(0\) 无法被抵消,那么这里也同样考虑这些 \(0\),一个数 \(a\) 模 \(p\) 意义下有逆元的充分必要条件是 \(\gcd(a,p)=1\)(容易通过扩展欧几里得求逆元的过程理解),所以我们考虑让阶乘中所有数都和 \({p_i}^{k_i}\) 互质,也就等价于与 \(p_i\) 互质,这样就能够求出所有阶乘的逆元,然后把所有提出来的 \(p_i\) 放在旁边算即可,下文 \(p_i\) 简写为 \(P\),\(k_i\) 简写为 \(K\),即:
也就变成了求:
我们先进行如下推导,把 \(n\) 阶乘里所有 \(P\) 的倍数提取出来:
考虑到 \(P^K\) 较小,我们只需要在 \(O(P^K)\) 用一个前缀积预处理出后面的东西,然后前面的 \(P\) 我们在 \(\frac{n!}{P^x}\) 是不希望看到它的,直接扔掉。中间还有一个 \((\lfloor\frac{n}{P}\rfloor !)\),里面还有可能包含 \(P\) 的倍数,那么我们递归处理就好。
边界条件:\(f(0)=1\)
我们考虑计算出 \(n!\) 里面因子 \(P\) 的数量 \(g(n)\),观察 \(f(n)\) 的转移式的来源发现其可以表示为:
边界条件:\(g(n)=0(n<P)\)
这里 \(f,g\) 的转移都是 \(O(\log_p)\) 级别的,至此我们就解决了计算部分,直接在原式中套用 \(f,g\) 即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,LL> PII;
PII exgcd(LL a,LL b){
if(b==0)return make_pair(1,0);
LL m=a/b,r=a%b;
PII res=exgcd(b,a%b);
LL x0=res.first,y0=res.second;
return make_pair(y0,x0-m*y0);
}
LL inv(LL a,LL b){
return (exgcd(a,b).first%b+b)%b;
}
int cnt,P[10],PK[10];
LL pre[(int)(1e6+5)];
void init(LL &p){
for(int i=2;i*i<=p;i++){
if(p%i==0)P[++cnt]=i,PK[cnt]=1;
while(p%i==0)p/=i,PK[cnt]*=i;
}
if(p>1)P[++cnt]=p,PK[cnt]=p;
}
LL qkpow(LL x,LL y,LL MOD){
LL res=1;
while(y){
if(y&1)res=res*x%MOD;
x=x*x%MOD;
y>>=1;
}
return res;
}
LL f(LL n,LL P,LL MOD){
if(!n)return 1;
LL r1=qkpow(pre[MOD],n/MOD,MOD);
LL r2=pre[n%MOD];
return f(n/P,P,MOD)*r1%MOD*r2%MOD;
}
LL g(LL n,LL P){
if(n<P)return 0;
return g(n/P,P)+(n/P);
}
LL a[10];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
LL m,n,p;
cin>>m>>n>>p;init(p);
for(int i=1;i<=cnt;i++){
pre[0]=pre[1]=1;
for(int j=2;j<=PK[i];j++){
pre[j]=pre[j-1];
if(j%P[i]!=0)pre[j]=pre[j]*j%PK[i];
}
a[i]=f(m,P[i],PK[i])*inv(f(n,P[i],PK[i]),PK[i])%PK[i]*
inv(f(m-n,P[i],PK[i]),PK[i])%PK[i]*
qkpow(P[i],g(m,P[i])-g(n,P[i])-g(m-n,P[i]),PK[i])%PK[i];
}
LL M=1,ans=0;
for(int i=1;i<=cnt;i++)M*=PK[i];
for(int i=1;i<=cnt;i++){
LL b=M/PK[i]*inv(M/PK[i],PK[i]);
ans=(ans+b*a[i]%M)%M;
}
cout<<ans;
return 0;
}
中国剩余定理(CRT)
一元线性同余方程的构造性解法。具体来说,对于方程:
其中 \(n_1,n_2,\dots,n_k\) 两两互质,可以采用如下方式构造解:
\(n=\prod_{i=1}^k n_i\)
-
\(m_i=\frac{n}{n_i}\)。
-
求出 \(m_i^{-1}\equiv \frac{1}{m_i}\pmod{n_i},c_i=m_i(m_i^{-1}\pmod{n_i})\)(\(c_i\) 不取模)
-
\(x\equiv\sum_{i=1}^k a_ic_i\pmod{n}\)
一般情况下由于不能取模该 \(m_i\) 会非常非常大。
Proof
考虑构造正确性证明,发现对于第 $i$ 个限制条件,$j\neq i$,有 $m_j\equiv 0\pmod{n_i}\implies c_j\equiv 0\pmod{n_i}$,而 $m_i\equiv 1\pmod{n_i},c_i\equiv 1\pmod{n_i}$,即 $\pmod{n_i}$ 意义下只有 $a_i$ 才会被计算,所以结果就是 $a_i$。代码中采用 exgcd 实现逆元,实际上求 \(a^{-1}\pmod{b}\) 就是求不定方程 \(ax-by=1\) 的解,\(b\) 位的正负并不重要,重要的是求到的 \(x\) 需要是正数,强制加 \(b\) 到最小的正数即可。
#include<bits/stdc++.h>
using namespace std;
typedef __int128 LL;
typedef pair<LL,LL> PII;
const int N=15;
PII exgcd(LL a,LL b){
if(b==0)return make_pair(1,0);
LL m=a/b,r=a%b;
PII res=exgcd(b,a%b);
LL x0=res.first,y0=res.second;
return make_pair(y0,x0-m*y0);
}
LL inv(LL a,LL b){
return (exgcd(a,b).first%b+b)%b;
}
long long n,a[N],b[N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i];
LL M=1;
long long ans=0;
for(int i=1;i<=n;i++)M*=a[i];
for(int i=1;i<=n;i++){
LL Mi=M/a[i],ti=inv(Mi,a[i])%M;
ans=(ans+b[i]%M*Mi%M*ti%M)%M;
}
cout<<ans;
return 0;
}
扩展中国剩余定理(exCRT)
考虑如何合并两个式子:
\(x\equiv a_1\pmod{m_1}\land x\equiv a_2\pmod{m_2}\)
不妨令:\(x=m_1p+a_1=m_2q+a_2\),此时 \(p,q\in\Z\),则 \(m_1p-m_2q=a_2-a_1\)。这是一个不定方程,可以使用扩展欧几里得求解,显然对于 \(\gcd(m_1,m_2)\nmid (a_2-a_1)\) 该方程无解,其余情况求解 \((p,q)\),然后就有合并后的 \(x\equiv m_1p+a_1\pmod{M},M=\text{lcm}(m_1,m_2)\),合并若干次即可得到一个合法解。
#include<bits/stdc++.h>
using namespace std;
typedef __int128 LL;
typedef pair<LL,LL> PII;
int n;
LL A1,M1,A2,M2;
long long a,m;
PII exgcd(LL a,LL b){
if(!b)return make_pair(1,0);
PII res=exgcd(b,a%b);
LL x0=res.first,y0=res.second;
return make_pair(y0,x0-y0*(a/b));
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>a;
A1=a,M1=m;
for(int i=2;i<=n;i++){
cin>>m>>a;
A2=a,M2=m;
LL d=__gcd(M1,M2);
assert((A2-A1)%d==0);
LL tms=(A2-A1)/d;
PII res=exgcd(M1,M2);
if(res.first<0)res.first=(res.first%M2+M2)%M2;
res.first*=tms;
A1=M1*res.first+A1;
M1=M1*M2/d;
A1=A1%M1;
}
cout<<(long long)((A1%M1+M1)%M1);
return 0;
}
容斥原理
我怎么没记过这东西。
容斥原理的本质是帮助我们消除一些限制条件,把真实的恰好方案数变成一些钦定方案数加加减减的结果。这样我们在实际考虑问题的时候,对于我们钦定的那些条件计数,没有钦定的条件满足或不满足都可以。
一个形象一点的模型,有一堆集合 \(S_1,S_2,...,S_n\),要迅速计算出它们并集的大小。已知:抽任意一个大小为 \(k\) 的 \(S\) 的集合,对于固定的 \(k\) 我们容易计算出其交集的大小和。那么答案就是:
计数范畴下,这个东西很类似二项式反演的 ② 形态,其实就是由它推导出了二项式反演。
证明:
考虑集合 \(S_i\) 中每个元素 \(A_j\) 的出现次数,如果 \(A_j\in S_{a_1},S_{a_2},...,S_{a_M}(M>0)\),那么它的出现次数就是:
二项式反演
本质上是容斥原理在计数领域的运用,适用于一些钦定与恰好的相互转换,这些转换通常都具有这样的特点:
-
\(n\) 个物品本质相同,所有物品都能成为被钦定的 \(i\) 个物品。
-
难以直接恰好满足某些条件,但是可以拆解为钦定满足/不满足一些条件,然后计数(容斥)。
其形式如下:
进一步地,有:
其中 ② 的 \(f(n)\) 表示恰好为 \(n\) 的情况数,\(F(n)\) 表示钦定为 \(n\) 的情况数。这里说明一下 ② 中 \(F(n)\) 具体定义表示在 \(m\) 个物品中选了任意 \(n\) 个,其他随便的方案数。很显然对于不同的 \(n\),方案会有重合,这时候就可以利用二项式反演去重。
这里的钦定和至少有一定区别,因为如果是至少它就应该是这样的:\(F(n)=\sum_{i=n}^mf(i)\),但显然这不是我们想要的东西,要不然就可以直接前缀和了。
那么钦定到底是什么?钦定有 \(x\) 个就是强制 \(x\) 个合法(\(x\) 个元素本质不同),其他合不合法都可以的有重计数(就是我们不考虑去重,直接把所有方案数加起来)。注意到由于其特殊性,一个恰有 \(y(y\geq x)\) 个合法的方案可以被 \(\begin{pmatrix}y\\x\end{pmatrix}\) 个钦定有 \(x\) 个统计到。
至少则是一种无重计数,即至少 \(x\) 个就是恰有 \(x,x+1,x+2,\dots\) 所有的方案数加起来。
如何证明二项式反演的正确性?先证明一个式子:
很自然的一个式子,由定义即可推出:
在 \(n\) 中选 \(i\),再在 \(i\) 中选 \(m\),相当于直接在 \(n\) 中选 \(m\),然后考虑这个 \(m\) 是由哪些 \(i\) 选到的,这里由于 \(m\) 中有的 \(i\) 中也一定有,所以考虑补集即可。
还有一个特殊二项式展开:
如何继续证明?考虑直接代入:
经典例题
BZOJ2839
考虑在 \(T\) 个集合内任意选非 \(0\) 个的方案数,很容易想到是 \(\sum_{i=1}^T\begin{pmatrix}T\\i\end{pmatrix}=-1+\sum_{i=0}^T\begin{pmatrix}T\\i\end{pmatrix}=2^T-1\),然后就相当于选定 \(k\) 位令其为 \(1\),然后剩下 \(n-k\) 位随意的所有集合(\(T=2^{n-k}\)),则 \(f(k)=\begin{pmatrix}n\\k\end{pmatrix}2^{2^{n-k}}-1\)。
P5505 [JSOI2011] 分特产
设计状态是一个很重要的步骤,如果第一步错了可能步步都错。本题是正难则反的思想,考虑到正面计数很困难,定 \(f(i)\) 为恰好有 \(i\) 个组没有特产,\(F(i)\) 为钦定有 \(i\) 个组没有特产,然后就可以用隔板法对于每件物品进行放置和计数。
P4859 已经没有什么好害怕的了
这个东西的主要难点在计数而不在设计。对于 \(A,B\) 的双射计数,这里的总方案数就是定下一个序列,然后另一个序列全排列 \(O(n!)\),非常爆炸。于是我们可以采用 DP 的方法来计算这个东西的方案数。先给 \(A,B\) 排个序,定 \(f_{i,j}\) 表示 \(A\) 序列中前 \(i\) 个,匹配了 \(j\) 对(定义一个匹配为 \(A_c>B_d\),且所有匹配不得有 \(c,d\) 互相重复),那么容易指针推一个 \(b\) 表示 \(B\) 中有多少个 \(B_d<A_i\)。然后就可以直接算了。有转移 \(f_{i,j}\leftarrow (b-(j-1))\times f_{i-1,j-1}\),还有后推 \(f_{i,j}\leftarrow f_{i-1,j}\),然后直接二项式反演即可。
威尔逊定理
一些常用的时间复杂度分析
调和级数

浙公网安备 33010602011771号