数论指南
数论
同余
同余是指:
- $a\times b\equiv (a\bmod p)\times(b\bmod p)\pmod p$
- $a+b\equiv (a\bmod p)+(b\bmod p)\pmod p$
- $a-b\equiv (a\bmod p)-(b\bmod p)\pmod p$
这个性质有助于我们推导数论公式和简化题目。比如有的题目要求答案对 $10^9+7$ 取模,但是计算的过程中随时可能爆 long long
,如果计算答案的过程中只包含 $+$、$-$、$\times$,就可以用同余来规避开高精度。
注意,$\div$ 并不满足同余性质,如果涉及到 $\div$ 的同余,那么就需要乘法逆元。
欧拉函数
欧拉函数是 $\varphi$ 函数,为 $1$ 到 $n$ 之内有多少个数与 $n$ 互质。即 $\varphi(n)=\sum_{i=1}^n[\gcd(i,n)=1]$。
$\varphi$ 具有如下性质。
- 积性,即对于任意的 $p,q\in Prime$,都有 $\varphi(p)\times\varphi(q)=\varphi(p\times q)$。
- 有这个性质:$(\sum_{d\mid n}\varphi(d))=n$。
- 欧拉定理,在后面会详解。
第一个性质可以用欧拉函数的求解方法来证明。$\varphi(p)=p-1$ 且仅当 $p\in Prime$。因为在 $[1,p]$ 之间,只有 $\gcd(p,p)=p$。
如果有 $n=p^2,p\in Prime$,那么 $\varphi(n)=p\times(p-1)$。因为有 $p$ 个数 $q$,$\gcd(n,q)=p$,所以是 $n-p$ 即 $p\times(p-1)$。
如果有 $n=p\times q,p,q\in Prime$,则可以用容斥原理证明。在 $[1,n]$ 中有 $\frac{n}{p}=q$ 个 $p$ 的倍数,$\frac{n}{q}=p$ 个 $q$ 的倍数,减去这些,发现有 $\frac{n}{p\times q}=1$ 个数要加上,即 $p\times q-p-q+1$,就是 $(p-1)\times(q-1)$,故得证。
再推得深入一些,如果 $n=q\times q\times r,p,q,r\in Prime$,那么就是 $n-\frac{n}{q}-\frac{n}{p}-\frac{n}{r}+\frac{n}{p\times q}+\frac{n}{p\times r}+\frac{n}{q\times r}-\frac{n}{p\times q\times r}=n-p\times q-p\times r-q\times r+p+q+r-1=(p-1)\times(q-1)\times(r-1)$。那么,我们可以推出公式,设唯一分解定理是:$n=\Pi_{p_i\in Prime}{m}p_i$,那么则有 $\varphi(n)=n\times\Pi_{p_i\in Prime}^{m}(\frac{p_i-1}{p_i})$,所以欧拉函数可以用欧拉筛法筛出来。
int phi[MAXN];
bool flag[MAXN];
vector<int> prim;
inline void prework(){
flag[1]=true;
phi[1]=1;
for(int i=2;i<MAXN;++i){
if(!flag[i]){
prim.push_back(i);
phi[i]=i-1;//p in Prime,则 phi(p)=p-1
}
for(int j=0;j<prim.size()&&i*prim[j]<MAXN;++j){//欧拉筛法
flag[i*prim[j]]=true;
if(i%prim[j]){
phi[i*prim[j]]=phi[i]*(prim[j]-1);//计算公式
}else{
phi[i*prim[j]]=phi[i]*prim[j];//不是重复的质数
break;
}
}
}
}
如何证明 $(\sum_{d\mid n}\varphi(d))=n$?
很明显,$[\gcd(n,m)=d]=[\gcd(\frac{n}{d},\frac{m}{d})=1]$,设 $f(x)=\sum_{i=1}^n[\gcd(i,n)=x]$,很明显,$n=\sum_{d\mid n}f(i)$。那么 $f(x)=\sum_{i=1}^{\lfloor\frac{n}{x}\rfloor}[\gcd(i,\lfloor\frac{n}{d}\rfloor)=1]$,那么就是 $f(x)=\varphi(\lfloor\frac{n}{x}\rfloor)$,故得证。这个性质可以用在欧拉反演中。
欧拉反演
我们利用性质 $2$:
$$n=\sum_{d\mid n}\varphi(d)$$
把 $n$ 替换成 $\gcd$:
$$\gcd(i,j)=\sum_{d\mid\gcd(i,j)}\varphi(d)$$
换成两个 $\sum$:
$$\gcd(i,j)=\sum_{d\mid i}\sum_{d\mid j}\varphi(d)$$
求 $\sum\gcd$:
$$\sum_{i=1}^n\gcd(i,n)=\sum_{d\mid n}\sum_{i=1}^n\sum_{d\mid i}\varphi(d)$$
再演化一下:
$$\sum_{i=1}^n\gcd(i,n)=\sum_{d\mid n}\varphi(d)\times\frac{n}{d}$$
得出结论,这就是欧拉反演。
例题
就是求出 $\sum_{i=1}n\sum_{j=1}n[\gcd(i,j)=1]$,不妨转化一下,变成 $(\sum_{i=1}n\sum_{j=1}i[\gcd(i,j)=1])\times 2-1$,这就变成了 $(\sum_{i=1}^{n-1}\varphi(i))\times 2+1$,求出 $\varphi$ 的前缀和就可以了。
#include<bits/stdc++.h>
#define MAXN 40004
using namespace std;
typedef long long ll;
int phi[MAXN];
ll pre[MAXN];
bool flag[MAXN];
vector<int> prim;
inline void prework(){
flag[1]=true;
phi[1]=1;
for(int i=2;i<MAXN;++i){
if(!flag[i]){
prim.push_back(i);
phi[i]=i-1;
}
for(int j=0;j<prim.size()&&i*prim[j]<MAXN;++j){
flag[i*prim[j]]=true;
if(i%prim[j]){
phi[i*prim[j]]=phi[i]*(prim[j]-1);
}else{
phi[i*prim[j]]=phi[i]*prim[j];
break;
}
}
}
for(int i=1;i<MAXN;++i){
pre[i]=pre[i-1]+phi[i];
}
}
int main(){
prework();
int n;
scanf("%d",&n);
printf("%lld",n==1?0ll:pre[n-1]<<1|1);
return 0;
}
欧拉定理
欧拉定理是指如果 $\gcd(n,m)=1$,则有 $n^{\varphi(m)}\equiv 1\pmod m$。
证明:构造一个集合 $S={1,2,3\dots\varphi(m)}$,那么则有 $\Pi_{i=1}{\varphi(m)-1}S_i\equiv\Pi_{i=1}(S_i\times n)\pmod m$,因为 $\gcd(\varphi(m),S_i)=1$,所以只有 $n$ 的影响。而 $\gcd(n,m)=1$,所以 $n$ 也没有影响。然后,同时消掉 $\Pi_{i=1}^{\varphi(m)-1}S_i$,则得 $n^{\varphi(m)}\equiv 1\pmod m$。
乘法逆元
如果 $a\times b\equiv 1\pmod p$,则称 $a$ 为 $b$ 模 $p$ 意义下的逆元,记作 $b^{-1}$。在模 $p$ 意义下,$a\times\frac{1}{b}\equiv a\times b^{-1}\pmod p$。$a$ 在模 $p$ 意义下有逆元存在且仅当 $\gcd(a,p)=1$。求乘法逆元有很多种方法。
快速幂求逆元
根据欧拉定理:
$$a^{p-1}\equiv a\times b^{-1}\pmod p$$
同时除以 $a$:
$$a^{p-2}\equiv b^{-1}\pmod p$$
所以 $b{-1}=a$,可以使用快速幂求。当 $\gcd(a,p-2)=1$ 的时候,可以用欧拉定理优化。
线性求逆元
这是递推求逆元的方式。很显然,$inv_1=1$。因为在任意模数下都有 $1\times 1\equiv 1\div 1\pmod p$。对于其他情况,我们令 $div=\lfloor\frac{p}{i}\rfloor$,$mod=p\bmod i$。
$$div\times i+mod\equiv 0\pmod p$$
两边同时乘以 $i^{-1}\times mod^{-1}$,即 $\frac{1}{i\times mod}$:
$$div\times mod{-1}+i\equiv 0\pmod p$$
同时减去 $div\times mod^{-1}$:
$$i^{-1}\equiv div\times mod^{-1}\equiv\pmod p$$
带入式子:
$$i^{-1}\equiv-\lfloor\frac{p}{i}\rfloor\times(p\bmod i)^{-1}\pmod p$$
可以知道 $p\bmod i<i$,所以可以应用之前求过的逆元,即:
$$inv_i\equiv-\lfloor\frac{p}{i}\rfloor\times inv_{p\bmod i}\pmod p$$
ll inv[MAXN];
inline void prework(int mod){//模数
inv[1]=1;//inv[1]=1 证明过
for(int i=2;i<MAXN;++i){//递推
inv[i]=((-(mod/i)*inv[mod%i])%mod+mod)%mod;//递推式
}
}
裴蜀定理
对于任意的整数 $a,b$ 和 $x,y$,满足 $\gcd(a,b)\mid a\times x+b\times y$,且存在 $x,y$ 满足 $a\times x+b\times y=\gcd(a,b)$。
对于第一点,一定满足 $\gcd(a,b)\mid a,b$,所以对于整数 $x,y$ 一定满足。
对于第二点,如果 $a\times b=0$,则 $\gcd(a,b)=\max(a,b)$,${x,y}={1,0}$ 一定满足。
如果 $a\times b\not=0$,则同时除以 $-\gcd(a,b)$,得 $(-a)\times x+(-b)\times y=1$。当 $\gcd(a,b)=1$ 的时候,可以证明消掉 $(x-y)\times(a-b)$,等于 $(a-b)\times(x-y)$,这一定是可以等于 $1$ 的。
这个定理对于 $n$ 个数一定也是正确的。
扩展欧几里得算法
和裴蜀定理类似,写作 $exgcd$,用于求裴蜀定理中的 $x$ 和 $y$。
这可以模拟 $\gcd$ 的过程。首先,$\gcd(a,b)=\gcd(a,a\bmod b)$,那么可以将 $x$ 和 $y$ 往回代:$a\times x+(a\bmod b)\times y=\gcd(a,b)$。
由于 $a\bmod b=a-\lfloor\frac{a}{b}\rfloor\times b$,那么再代入式中:$a\times x+(a-\lfloor\frac{a}{b}\rfloor\times b)\times y=\gcd(a,b)$,演化一下:
$$a\times(x+y)-\lfloor\frac{a}{b}\rfloor\times b\times y=\gcd(a,b)$$
之后,就可以带入求 $exgcd$ 了。
void exgcd(ll a,ll b,ll &x,ll &y){
if(!b){
x=1;
y=0;
return;//可行解
}
exgcd(b,a%b);//带入 gcd
ll t=x;
x=y;
y=t-a/b*y;//带入式子
}
这一个性质也可以帮助我们求逆元,但是过于复杂而且有同样复杂度 $\log n$ 的快速幂,所以这里不展开介绍。
组合数学
多重集问题
先从最基础的开始。比如给你 $n$ 个数,要求从中选择 $m$ 个数,分顺序,有多少种选择?很明显,这个是 $\Pi_{i=m}^n i$ 种选择方案,即 $\frac{n!}{(n-m)!}$。我们称排列 $A_n^m=\frac{n!}{(n-m)!}$。
假如给你 $n$ 个数,要求从中选择 $m$ 个数,有多少种不同的选择方案?考虑化繁为简。首先,分顺序的是 $A_n^m$ 种,然后要除以顺序所带来的价值,也就是 $m$ 个数从中选择 $m-1$ 个数,即 $A_{m-1}^m=m!$,那么得出 $C_nm=\frac{A_nm}{A_m^1}=\frac{n!}{m!\times(n-m)!}$。
那么假如有 $k$ 个集合,每一个集合都有 $c_i$ 个 $p_i$,那么这 $n$ 个集合的并集中选出 $m$ 个数的个数。设 $n=\sum_{i=1}^{k}c_i$,那么第一项肯定是 $A_n^m$。接下来,要依次按照上面的做法除以 $A_{c_i}^1$,所以就是 $\frac{A_nm}{\Pi_{i=1}A_{c_i}^1}$。
当然,还有一种题型叫做插板法,就是在 $n$ 个元素内插入 $m$ 个板子,将这 $n$ 个元素分成 $m+1$ 个块,求方案数。
由于元素完全相同,所以可以直接得出答案为 $C_m^n$。这是一个结论,和欧拉反演的结论一样,都是助于把题目转换为插板法的形式,进行更优秀的解答。
如何求出 $C$?有几种方法。
$C$ 递推式
很明显,可以看出 $C$ 就是杨辉三角,可以使用杨辉三角进行递推。
ll C[MAXN][MAXN];
inline void prework(){
C[1][1]=1;
for(int i=2;i<MAXN;++i){
C[i][1]=C[i][i]=1;//两端为 1
for(int j=2;j<i;++j){
C[i][j]=C[i-1][j-1]+C[i-1][j];//杨辉三角
}
}
}
阶乘逆元递推式
可以直接套用公式,如果要取模并且可以使用逆元,那就可以用逆元。
ll inv[MAXN],frac[MAXN];//有时候是只能够现场求逆元和阶乘,因为逆元存不下
inline ll C(int n,int m){
if(n<m){
return 0;//特判
}
return frac[n]*inv[frac[m]]%MOD*inv[frac[n-m]]%MOD;//公式
}
鸽巢原理
鸽巢原理是指有 $m$ 个鸽巢和 $n$ 只鸽子,每一只鸽子要飞进鸽巢中,至少有 $1$ 个鸽巢至少有 $\lceil\frac{n}{m}\rceil$ 只鸽子。
考虑反证法。如果每一个鸽巢最多有 $\lfloor\frac{n}{m}\rfloor$ 只鸽子,那么最多有 $\lfloor\frac{n}{m}\rfloor\times m$ 只鸽子。但是这可能不是 $n$,多以矛盾。
容斥原理
容斥原理适用于求多个集合的集合并的大小用的。假如有 $n$ 个集合 $S_i$,那么要求 $|S_1\cup S_2\cup S_3\dots S_n|$,首先可以把所有元素的大小加和,即 $\sum_{i=1}^{n}|S_i|$,可以得其中有元素重复了,那么减去一些元素的交集,$|S1\cap S_2|+|S_2\cap S_3|\dots|S_n\cap S_1|$,则有一些元素多减了。再加上 $|S_1\cap S_2\cap S_3|\dots |S_{n-1}\cap S_n\cap S_1|$,又发现有一些多加了,重复执行此操作直至发现 $\cap_{i=1}^{n}S_i$ 多加或者多减。通过上述过程证明:
$$|\cup_{i=1}{n}S_i|=\sum_{m=1}(-1){(m-1)}\times(\sum_{a_i<a_{i+1}})|\cap_{i=1}S_{a_i}|$$
其中,每一个 $a_i$ 代表枚举顺序。
容斥原理的难点在于枚举顺序和确定 $|S_i|$。有的时候,$|S_i|$ 为 dp 式。有的时候,$|S_i|$ 为函数。容斥原理比 dp 更加难操作和推理,非常棘手。
例题
题目要求求出:
$$\sum_{a_i=x}^{y}[\gcd(a_i)=x][\operatorname{lcm}(a_i)=y]$$
不难发现,每一个数必须在 $x$ 到 $y$ 之间,每一个数可以表示为 $t\times\frac{y}{x}(t\le x)$。不同的就是这一个 $t$。
设 $t=\frac{y}{x}$,并且设 $t=\Pi_{i=1}{k}p_i(p_i\in Prime)$,那么可以证明,要求 $\gcd$ 为 $x$,那么至少要有一个数,其 $c_i$ 为 $0$。要求 $\operatorname{lcm}$ 为 $y$,那么至少要有一个数,其 $c_i$ 为 $c_i$。答案为 $(c_i+1)^n$。
那么,发现是至少,因此要减去所有 $c_i$ 为 $[1,c_i]$ 的情况,$c_i$ 为 $[0,c_i-1]$ 的情况同理,要减一次。
再发现 $[1,c_i-1]$ 被减了两次,那么加回来,得答案为 $(c_i+1)n-2\times(c_i)n+(c_i-1)^n$。之后对于 $p_i$ 不同考虑乘起来即可。
#include<bits/stdc++.h>
#define MAXN 350000
#define MOD 998244353
using namespace std;
typedef long long ll;
int n,x,y,top,p[MAXN],c[MAXN];
bool flag[MAXN];
vector<int> prim;
inline void prework(){
flag[1]=true;
for(int i=2;i<MAXN;++i){
if(!flag[i]){
prim.push_back(i);
}
for(int j=0;j<prim.size()&&i*prim[j]<MAXN;++j){
flag[i*prim[j]]=true;
if(i%prim[j]==0){
break;
}
}
}
}
inline bool check(int x){
if(x<=1){
return false;
}
for(int i=2;i*i<=x;++i){
if(x%i==0){
return false;
}
}
return true;
}
inline ll power(ll x,ll y){
ll res=1;
while(y){
if(y&1){
(res*=x)%=MOD;
}
(x*=x)%=MOD;
y>>=1;
}
return res;
}
inline void split(int x){
top=0;
for(int i=0;i<prim.size()&&x!=1;++i){
if(x%prim[i]==0){
p[++top]=prim[i];
c[top]=0;
while(x%prim[i]==0){
x/=prim[i];
++c[top];
}
}
}
if(check(x)){
p[++top]=x;
c[top]=1;
}
}
int main(){
prework();
int t;
scanf("%d",&t);
while(t--){
scanf("%d %d %d",&x,&y,&n);
int t=y/x;
split(t);
ll ans=1;
for(int i=1;i<=top;++i){
ll a=power(c[i]+1,n);
ll b=power(c[i],n);
ll d=power(c[i]-1,n);
(ans*=((a-2ll*b+d)%MOD+MOD)%MOD)%=MOD;
}
printf("%lld\n",ans);
}
return 0;
}
卢卡斯定理
卢卡斯定理是用于求 $C$ 的。假如要求 $C_n^m\bmod p$,并且 $p$ 为质数,那么则有 $C_n^m\equiv C_{n\bmod p}^{m\bmod p}\times C_{\lfloor\frac{n}{p}\rfloor}^{\lfloor\frac{m}{p}\rfloor}\pmod p$。我们发现,如果 $n$ 和 $m$ 非常大的时候,可以用卢卡斯定理缩小 $n$ 和 $m$,右边的除法式还可以继续用卢卡斯,时间复杂度为 $n\log p$。
证明右转 OI Wiki,我不会告诉你我不会证明。
扩展卢卡斯定理
这是在 $p$ 不是质数的情况下使用的。主要思想是将 $p$ 分解质因数,分别 $lucas$,然后用中国剩余定理合并答案。
例题
很明显,题目要求的是具有小根堆性质的数列。设 $dp_i$ 为 $i$ 个数的排列中满足小根堆性质的个数。设 $i$ 为根节点,那么有 $i-1$ 个点是子节点。那么从 $i-1$ 个节点中选择 $l$ 个节点为左子节点,为 $C_{i-1}^l$,那么得出 $dp_i=C_{son_i-1}^{son_{\lfloor\frac{i}{2}\rfloor}}\times dp_{i\times 2}\times dp_{i\times 2+1}$。其中,$son_i$ 表示在二叉树下 $i$ 的子节点个数。
#include<bits/stdc++.h>
#define MAXN 2000002
using namespace std;
typedef long long ll;
int n,p,son[MAXN];
ll frac[MAXN],pre[MAXN],dp[MAXN];
inline void prework(){
frac[0]=1;
for(int i=1;i<MAXN;++i){
dp[i]=1;
frac[i]=frac[i-1]*i%p;
}
for(int i=n;i>=2;--i){
++son[i];
son[i>>1]+=son[i];
}
++son[1];
}
inline ll power(ll x,ll y){
ll res=1;
while(y){
if(y&1){
(res*=x)%=p;
}
(x*=x)%=p;
y>>=1;
}
return res;
}
ll C(int n,int m){
if(m>n){
return 0;
}
return frac[n]*power(frac[m],p-2)%p*power(frac[n-m],p-2)%p;
}
ll lucas(int n,int m){
if(!m){
return 1;
}
return C(n%p,m%p)*lucas(n/p,m/p)%p;
}
int main(){
scanf("%d %d",&n,&p);
prework();
for(int i=n;i>=1;--i){
dp[i]=lucas(son[i]-1,son[i<<1])*dp[i<<1]%p*dp[i<<1|1]%p;
}
printf("%lld",dp[1]);
}