数论
同余
同余是指:
- a × b ≡ ( a m o d p ) × ( b m o d p ) ( m o d p ) a\times b\equiv (a\bmod p)\times(b\bmod p)\pmod p a×b≡(amodp)×(bmodp)(modp)
- a + b ≡ ( a m o d p ) + ( b m o d p ) ( m o d p ) a+b\equiv (a\bmod p)+(b\bmod p)\pmod p a+b≡(amodp)+(bmodp)(modp)
- a − b ≡ ( a m o d p ) − ( b m o d p ) ( m o d p ) a-b\equiv (a\bmod p)-(b\bmod p)\pmod p a−b≡(amodp)−(bmodp)(modp)
这个性质有助于我们推导数论公式和简化题目。比如有的题目要求答案对
1
0
9
+
7
10^9+7
109+7 取模,但是计算的过程中随时可能爆 long long
,如果计算答案的过程中只包含
+
+
+、
−
-
−、
×
\times
×,就可以用同余来规避开高精度。
注意, ÷ \div ÷ 并不满足同余性质,如果涉及到 ÷ \div ÷ 的同余,那么就需要乘法逆元。
欧拉函数
欧拉函数是 φ \varphi φ 函数,为 1 1 1 到 n n n 之内有多少个数与 n n n 互质。即 φ ( n ) = ∑ i = 1 n [ gcd ( i , n ) = 1 ] \varphi(n)=\sum_{i=1}^n[\gcd(i,n)=1] φ(n)=∑i=1n[gcd(i,n)=1]。
φ \varphi φ 具有如下性质。
- 积性,即对于任意的 p , q ∈ P r i m e p,q\in Prime p,q∈Prime,都有 φ ( p ) × φ ( q ) = φ ( p × q ) \varphi(p)\times\varphi(q)=\varphi(p\times q) φ(p)×φ(q)=φ(p×q)。
- 有这个性质: ( ∑ d ∣ n φ ( d ) ) = n (\sum_{d\mid n}\varphi(d))=n (∑d∣nφ(d))=n。
- 欧拉定理,在后面会详解。
第一个性质可以用欧拉函数的求解方法来证明。 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p−1 且仅当 p ∈ P r i m e p\in Prime p∈Prime。因为在 [ 1 , p ] [1,p] [1,p] 之间,只有 gcd ( p , p ) = p \gcd(p,p)=p gcd(p,p)=p。
如果有 n = p 2 , p ∈ P r i m e n=p^2,p\in Prime n=p2,p∈Prime,那么 φ ( n ) = p × ( p − 1 ) \varphi(n)=p\times(p-1) φ(n)=p×(p−1)。因为有 p p p 个数 q q q, gcd ( n , q ) = p \gcd(n,q)=p gcd(n,q)=p,所以是 n − p n-p n−p 即 p × ( p − 1 ) p\times(p-1) p×(p−1)。
如果有 n = p × q , p , q ∈ P r i m e n=p\times q,p,q\in Prime n=p×q,p,q∈Prime,则可以用容斥原理证明。在 [ 1 , n ] [1,n] [1,n] 中有 n p = q \frac{n}{p}=q pn=q 个 p p p 的倍数, n q = p \frac{n}{q}=p qn=p 个 q q q 的倍数,减去这些,发现有 n p × q = 1 \frac{n}{p\times q}=1 p×qn=1 个数要加上,即 p × q − p − q + 1 p\times q-p-q+1 p×q−p−q+1,就是 ( p − 1 ) × ( q − 1 ) (p-1)\times(q-1) (p−1)×(q−1),故得证。
再推得深入一些,如果 n = q × q × r , p , q , r ∈ P r i m e n=q\times q\times r,p,q,r\in Prime n=q×q×r,p,q,r∈Prime,那么就是 n − n q − n p − n r + n p × q + n p × r + n q × r − n p × q × r = n − p × q − p × r − q × r + p + q + r − 1 = ( p − 1 ) × ( q − 1 ) × ( r − 1 ) 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−qn−pn−rn+p×qn+p×rn+q×rn−p×q×rn=n−p×q−p×r−q×r+p+q+r−1=(p−1)×(q−1)×(r−1)。那么,我们可以推出公式,设唯一分解定理是: n = Π p i ∈ P r i m e m p i c i n=\Pi_{p_i\in Prime}^{m}p_i^{c_i} n=Πpi∈Primempici,那么则有 φ ( n ) = n × Π p i ∈ P r i m e m ( p i − 1 p i ) \varphi(n)=n\times\Pi_{p_i\in Prime}^{m}(\frac{p_i-1}{p_i}) φ(n)=n×Πpi∈Primem(pipi−1),所以欧拉函数可以用欧拉筛法筛出来。
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;
}
}
}
}
如何证明 ( ∑ d ∣ n φ ( d ) ) = n (\sum_{d\mid n}\varphi(d))=n (∑d∣nφ(d))=n?
很明显, [ gcd ( n , m ) = d ] = [ gcd ( n d , m d ) = 1 ] [\gcd(n,m)=d]=[\gcd(\frac{n}{d},\frac{m}{d})=1] [gcd(n,m)=d]=[gcd(dn,dm)=1],设 f ( x ) = ∑ i = 1 n [ gcd ( i , n ) = x ] f(x)=\sum_{i=1}^n[\gcd(i,n)=x] f(x)=∑i=1n[gcd(i,n)=x],很明显, n = ∑ d ∣ n f ( i ) n=\sum_{d\mid n}f(i) n=∑d∣nf(i)。那么 f ( x ) = ∑ i = 1 ⌊ n x ⌋ [ gcd ( i , ⌊ n d ⌋ ) = 1 ] f(x)=\sum_{i=1}^{\lfloor\frac{n}{x}\rfloor}[\gcd(i,\lfloor\frac{n}{d}\rfloor)=1] f(x)=∑i=1⌊xn⌋[gcd(i,⌊dn⌋)=1],那么就是 f ( x ) = φ ( ⌊ n x ⌋ ) f(x)=\varphi(\lfloor\frac{n}{x}\rfloor) f(x)=φ(⌊xn⌋),故得证。这个性质可以用在欧拉反演中。
欧拉反演
我们利用性质
2
2
2:
n
=
∑
d
∣
n
φ
(
d
)
n=\sum_{d\mid n}\varphi(d)
n=d∣n∑φ(d)
把
n
n
n 替换成
gcd
\gcd
gcd:
gcd
(
i
,
j
)
=
∑
d
∣
gcd
(
i
,
j
)
φ
(
d
)
\gcd(i,j)=\sum_{d\mid\gcd(i,j)}\varphi(d)
gcd(i,j)=d∣gcd(i,j)∑φ(d)
换成两个
∑
\sum
∑:
gcd
(
i
,
j
)
=
∑
d
∣
i
∑
d
∣
j
φ
(
d
)
\gcd(i,j)=\sum_{d\mid i}\sum_{d\mid j}\varphi(d)
gcd(i,j)=d∣i∑d∣j∑φ(d)
求
∑
gcd
\sum\gcd
∑gcd:
∑
i
=
1
n
gcd
(
i
,
n
)
=
∑
d
∣
n
∑
i
=
1
n
∑
d
∣
i
φ
(
d
)
\sum_{i=1}^n\gcd(i,n)=\sum_{d\mid n}\sum_{i=1}^n\sum_{d\mid i}\varphi(d)
i=1∑ngcd(i,n)=d∣n∑i=1∑nd∣i∑φ(d)
再演化一下:
∑
i
=
1
n
gcd
(
i
,
n
)
=
∑
d
∣
n
φ
(
d
)
×
n
d
\sum_{i=1}^n\gcd(i,n)=\sum_{d\mid n}\varphi(d)\times\frac{n}{d}
i=1∑ngcd(i,n)=d∣n∑φ(d)×dn
得出结论,这就是欧拉反演。
例题
就是求出 ∑ i = 1 n ∑ j = 1 n [ gcd ( i , j ) = 1 ] \sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=1] ∑i=1n∑j=1n[gcd(i,j)=1],不妨转化一下,变成 ( ∑ i = 1 n ∑ j = 1 i [ gcd ( i , j ) = 1 ] ) × 2 − 1 (\sum_{i=1}^n\sum_{j=1}^i[\gcd(i,j)=1])\times 2-1 (∑i=1n∑j=1i[gcd(i,j)=1])×2−1,这就变成了 ( ∑ i = 1 n − 1 φ ( i ) ) × 2 + 1 (\sum_{i=1}^{n-1}\varphi(i))\times 2+1 (∑i=1n−1φ(i))×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 \gcd(n,m)=1 gcd(n,m)=1,则有 n φ ( m ) ≡ 1 ( m o d m ) n^{\varphi(m)}\equiv 1\pmod m nφ(m)≡1(modm)。
证明:构造一个集合 S = 1 , 2 , 3 … φ ( m ) S={1,2,3\dots\varphi(m)} S=1,2,3…φ(m),那么则有 Π i = 1 φ ( m ) − 1 S i ≡ Π i = 1 φ ( m ) − 1 ( S i × n ) ( m o d m ) \Pi_{i=1}^{\varphi(m)-1}S_i\equiv\Pi_{i=1}^{\varphi(m)-1}(S_i\times n)\pmod m Πi=1φ(m)−1Si≡Πi=1φ(m)−1(Si×n)(modm),因为 gcd ( φ ( m ) , S i ) = 1 \gcd(\varphi(m),S_i)=1 gcd(φ(m),Si)=1,所以只有 n n n 的影响。而 gcd ( n , m ) = 1 \gcd(n,m)=1 gcd(n,m)=1,所以 n n n 也没有影响。然后,同时消掉 Π i = 1 φ ( m ) − 1 S i \Pi_{i=1}^{\varphi(m)-1}S_i Πi=1φ(m)−1Si,则得 n φ ( m ) ≡ 1 ( m o d m ) n^{\varphi(m)}\equiv 1\pmod m nφ(m)≡1(modm)。
乘法逆元
如果 a × b ≡ 1 ( m o d p ) a\times b\equiv 1\pmod p a×b≡1(modp),则称 a a a 为 b b b 模 p p p 意义下的逆元,记作 b − 1 b^{-1} b−1。在模 p p p 意义下, a × 1 b ≡ a × b − 1 ( m o d p ) a\times\frac{1}{b}\equiv a\times b^{-1}\pmod p a×b1≡a×b−1(modp)。 a a a 在模 p p p 意义下有逆元存在且仅当 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1。求乘法逆元有很多种方法。
快速幂求逆元
根据欧拉定理:
a
p
−
1
≡
a
×
b
−
1
(
m
o
d
p
)
a^{p-1}\equiv a\times b^{-1}\pmod p
ap−1≡a×b−1(modp)
同时除以
a
a
a:
a
p
−
2
≡
b
−
1
(
m
o
d
p
)
a^{p-2}\equiv b^{-1}\pmod p
ap−2≡b−1(modp)
所以
b
−
1
=
a
p
−
2
b^{-1}=a^{p-2}
b−1=ap−2,可以使用快速幂求。当
gcd
(
a
,
p
−
2
)
=
1
\gcd(a,p-2)=1
gcd(a,p−2)=1 的时候,可以用欧拉定理优化。
线性求逆元
这是递推求逆元的方式。很显然,
i
n
v
1
=
1
inv_1=1
inv1=1。因为在任意模数下都有
1
×
1
≡
1
÷
1
(
m
o
d
p
)
1\times 1\equiv 1\div 1\pmod p
1×1≡1÷1(modp)。对于其他情况,我们令
d
i
v
=
⌊
p
i
⌋
div=\lfloor\frac{p}{i}\rfloor
div=⌊ip⌋,
m
o
d
=
p
m
o
d
i
mod=p\bmod i
mod=pmodi。
d
i
v
×
i
+
m
o
d
≡
0
(
m
o
d
p
)
div\times i+mod\equiv 0\pmod p
div×i+mod≡0(modp)
两边同时乘以
i
−
1
×
m
o
d
−
1
i^{-1}\times mod^{-1}
i−1×mod−1,即
1
i
×
m
o
d
\frac{1}{i\times mod}
i×mod1:
d
i
v
×
m
o
d
−
1
+
i
−
1
≡
0
(
m
o
d
p
)
div\times mod^{-1}+i^{-1}\equiv 0\pmod p
div×mod−1+i−1≡0(modp)
同时减去
d
i
v
×
m
o
d
−
1
div\times mod^{-1}
div×mod−1:
i
−
1
≡
d
i
v
×
m
o
d
−
1
≡
(
m
o
d
p
)
i^{-1}\equiv div\times mod^{-1}\equiv\pmod p
i−1≡div×mod−1≡(modp)
带入式子:
i
−
1
≡
−
⌊
p
i
⌋
×
(
p
m
o
d
i
)
−
1
(
m
o
d
p
)
i^{-1}\equiv-\lfloor\frac{p}{i}\rfloor\times(p\bmod i)^{-1}\pmod p
i−1≡−⌊ip⌋×(pmodi)−1(modp)
可以知道
p
m
o
d
i
<
i
p\bmod i<i
pmodi<i,所以可以应用之前求过的逆元,即:
i
n
v
i
≡
−
⌊
p
i
⌋
×
i
n
v
p
m
o
d
i
(
m
o
d
p
)
inv_i\equiv-\lfloor\frac{p}{i}\rfloor\times inv_{p\bmod i}\pmod p
invi≡−⌊ip⌋×invpmodi(modp)
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 a,b a,b 和 x , y x,y x,y,满足 gcd ( a , b ) ∣ a × x + b × y \gcd(a,b)\mid a\times x+b\times y gcd(a,b)∣a×x+b×y,且存在 x , y x,y x,y 满足 a × x + b × y = gcd ( a , b ) a\times x+b\times y=\gcd(a,b) a×x+b×y=gcd(a,b)。
对于第一点,一定满足 gcd ( a , b ) ∣ a , b \gcd(a,b)\mid a,b gcd(a,b)∣a,b,所以对于整数 x , y x,y x,y 一定满足。
对于第二点,如果 a × b = 0 a\times b=0 a×b=0,则 gcd ( a , b ) = max ( a , b ) \gcd(a,b)=\max(a,b) gcd(a,b)=max(a,b), { x , y } = { 1 , 0 } \{x,y\}=\{1,0\} {x,y}={1,0} 一定满足。
如果 a × b ≠ 0 a\times b\not=0 a×b=0,则同时除以 − gcd ( a , b ) -\gcd(a,b) −gcd(a,b),得 ( − a ) × x + ( − b ) × y = 1 (-a)\times x+(-b)\times y=1 (−a)×x+(−b)×y=1。当 gcd ( a , b ) = 1 \gcd(a,b)=1 gcd(a,b)=1 的时候,可以证明消掉 ( x − y ) × ( a − b ) (x-y)\times(a-b) (x−y)×(a−b),等于 ( a − b ) × ( x − y ) (a-b)\times(x-y) (a−b)×(x−y),这一定是可以等于 1 1 1 的。
这个定理对于 n n n 个数一定也是正确的。
扩展欧几里得算法
和裴蜀定理类似,写作 e x g c d exgcd exgcd,用于求裴蜀定理中的 x x x 和 y y y。
这可以模拟 gcd \gcd gcd 的过程。首先, gcd ( a , b ) = gcd ( a , a m o d b ) \gcd(a,b)=\gcd(a,a\bmod b) gcd(a,b)=gcd(a,amodb),那么可以将 x x x 和 y y y 往回代: a × x + ( a m o d b ) × y = gcd ( a , b ) a\times x+(a\bmod b)\times y=\gcd(a,b) a×x+(amodb)×y=gcd(a,b)。
由于
a
m
o
d
b
=
a
−
⌊
a
b
⌋
×
b
a\bmod b=a-\lfloor\frac{a}{b}\rfloor\times b
amodb=a−⌊ba⌋×b,那么再代入式中:
a
×
x
+
(
a
−
⌊
a
b
⌋
×
b
)
×
y
=
gcd
(
a
,
b
)
a\times x+(a-\lfloor\frac{a}{b}\rfloor\times b)\times y=\gcd(a,b)
a×x+(a−⌊ba⌋×b)×y=gcd(a,b),演化一下:
a
×
(
x
+
y
)
−
⌊
a
b
⌋
×
b
×
y
=
gcd
(
a
,
b
)
a\times(x+y)-\lfloor\frac{a}{b}\rfloor\times b\times y=\gcd(a,b)
a×(x+y)−⌊ba⌋×b×y=gcd(a,b)
之后,就可以带入求 e x g c d exgcd 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 \log n logn 的快速幂,所以这里不展开介绍。
组合数学
多重集问题
先从最基础的开始。比如给你 n n n 个数,要求从中选择 m m m 个数,分顺序,有多少种选择?很明显,这个是 Π i = m n i \Pi_{i=m}^n i Πi=mni 种选择方案,即 n ! ( n − m ) ! \frac{n!}{(n-m)!} (n−m)!n!。我们称排列 A n m = n ! ( n − m ) ! A_n^m=\frac{n!}{(n-m)!} Anm=(n−m)!n!。
假如给你 n n n 个数,要求从中选择 m m m 个数,有多少种不同的选择方案?考虑化繁为简。首先,分顺序的是 A n m A_n^m Anm 种,然后要除以顺序所带来的价值,也就是 m m m 个数从中选择 m − 1 m-1 m−1 个数,即 A m − 1 m = m ! A_{m-1}^m=m! Am−1m=m!,那么得出 C n m = A n m A m 1 = n ! m ! × ( n − m ) ! C_n^m=\frac{A_n^m}{A_m^1}=\frac{n!}{m!\times(n-m)!} Cnm=Am1Anm=m!×(n−m)!n!。
那么假如有 k k k 个集合,每一个集合都有 c i c_i ci 个 p i p_i pi,那么这 n n n 个集合的并集中选出 m m m 个数的个数。设 n = ∑ i = 1 k c i n=\sum_{i=1}^{k}c_i n=∑i=1kci,那么第一项肯定是 A n m A_n^m Anm。接下来,要依次按照上面的做法除以 A c i 1 A_{c_i}^1 Aci1,所以就是 A n m Π i = 1 k A c i 1 \frac{A_n^m}{\Pi_{i=1}^{k}A_{c_i}^1} Πi=1kAci1Anm。
当然,还有一种题型叫做插板法,就是在 n n n 个元素内插入 m m m 个板子,将这 n n n 个元素分成 m + 1 m+1 m+1 个块,求方案数。
由于元素完全相同,所以可以直接得出答案为 C m n C_m^n Cmn。这是一个结论,和欧拉反演的结论一样,都是助于把题目转换为插板法的形式,进行更优秀的解答。
如何求出 C C C?有几种方法。
C C C 递推式
很明显,可以看出 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 m m 个鸽巢和 n n n 只鸽子,每一只鸽子要飞进鸽巢中,至少有 1 1 1 个鸽巢至少有 ⌈ n m ⌉ \lceil\frac{n}{m}\rceil ⌈mn⌉ 只鸽子。
考虑反证法。如果每一个鸽巢最多有 ⌊ n m ⌋ \lfloor\frac{n}{m}\rfloor ⌊mn⌋ 只鸽子,那么最多有 ⌊ n m ⌋ × m \lfloor\frac{n}{m}\rfloor\times m ⌊mn⌋×m 只鸽子。但是这可能不是 n n n,多以矛盾。
容斥原理
容斥原理适用于求多个集合的集合并的大小用的。假如有
n
n
n 个集合
S
i
S_i
Si,那么要求
∣
S
1
∪
S
2
∪
S
3
…
S
n
∣
|S_1\cup S_2\cup S_3\dots S_n|
∣S1∪S2∪S3…Sn∣,首先可以把所有元素的大小加和,即
∑
i
=
1
n
∣
S
i
∣
\sum_{i=1}^{n}|S_i|
∑i=1n∣Si∣,可以得其中有元素重复了,那么减去一些元素的交集,
∣
S
1
∩
S
2
∣
+
∣
S
2
∩
S
3
∣
…
∣
S
n
∩
S
1
∣
|S1\cap S_2|+|S_2\cap S_3|\dots|S_n\cap S_1|
∣S1∩S2∣+∣S2∩S3∣…∣Sn∩S1∣,则有一些元素多减了。再加上
∣
S
1
∩
S
2
∩
S
3
∣
…
∣
S
n
−
1
∩
S
n
∩
S
1
∣
|S_1\cap S_2\cap S_3|\dots |S_{n-1}\cap S_n\cap S_1|
∣S1∩S2∩S3∣…∣Sn−1∩Sn∩S1∣,又发现有一些多加了,重复执行此操作直至发现
∩
i
=
1
n
S
i
\cap_{i=1}^{n}S_i
∩i=1nSi 多加或者多减。通过上述过程证明:
∣
∪
i
=
1
n
S
i
∣
=
∑
m
=
1
n
(
−
1
)
(
m
−
1
)
×
(
∑
a
i
<
a
i
+
1
)
∣
∩
i
=
1
m
S
a
i
∣
|\cup_{i=1}^{n}S_i|=\sum_{m=1}^{n}(-1)^{(m-1)}\times(\sum_{a_i<a_{i+1}})|\cap_{i=1}^{m}S_{a_i}|
∣∪i=1nSi∣=m=1∑n(−1)(m−1)×(ai<ai+1∑)∣∩i=1mSai∣
其中,每一个
a
i
a_i
ai 代表枚举顺序。
容斥原理的难点在于枚举顺序和确定 ∣ S i ∣ |S_i| ∣Si∣。有的时候, ∣ S i ∣ |S_i| ∣Si∣ 为 dp 式。有的时候, ∣ S i ∣ |S_i| ∣Si∣ 为函数。容斥原理比 dp 更加难操作和推理,非常棘手。
例题
题目要求求出:
∑
a
i
=
x
y
[
gcd
(
a
i
)
=
x
]
[
lcm
(
a
i
)
=
y
]
\sum_{a_i=x}^{y}[\gcd(a_i)=x][\operatorname{lcm}(a_i)=y]
ai=x∑y[gcd(ai)=x][lcm(ai)=y]
不难发现,每一个数必须在 x x x 到 y y y 之间,每一个数可以表示为 t × y x ( t ≤ x ) t\times\frac{y}{x}(t\le x) t×xy(t≤x)。不同的就是这一个 t t t。
设 t = y x t=\frac{y}{x} t=xy,并且设 t = Π i = 1 k p i c i ( p i ∈ P r i m e ) t=\Pi_{i=1}^{k}p_i^{c_i}(p_i\in Prime) t=Πi=1kpici(pi∈Prime),那么可以证明,要求 gcd \gcd gcd 为 x x x,那么至少要有一个数,其 c i c_i ci 为 0 0 0。要求 lcm \operatorname{lcm} lcm 为 y y y,那么至少要有一个数,其 c i c_i ci 为 c i c_i ci。答案为 ( c i + 1 ) n (c_i+1)^n (ci+1)n。
那么,发现是至少,因此要减去所有 c i c_i ci 为 [ 1 , c i ] [1,c_i] [1,ci] 的情况, c i c_i ci 为 [ 0 , c i − 1 ] [0,c_i-1] [0,ci−1] 的情况同理,要减一次。
再发现 [ 1 , c i − 1 ] [1,c_i-1] [1,ci−1] 被减了两次,那么加回来,得答案为 ( c i + 1 ) n − 2 × ( c i ) n + ( c i − 1 ) n (c_i+1)^n-2\times(c_i)^n+(c_i-1)^n (ci+1)n−2×(ci)n+(ci−1)n。之后对于 p i p_i pi 不同考虑乘起来即可。
#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 C 的。假如要求 C n m m o d p C_n^m\bmod p Cnmmodp,并且 p p p 为质数,那么则有 C n m ≡ C n m o d p m m o d p × C ⌊ n p ⌋ ⌊ m p ⌋ ( m o d 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 Cnm≡Cnmodpmmodp×C⌊pn⌋⌊pm⌋(modp)。我们发现,如果 n n n 和 m m m 非常大的时候,可以用卢卡斯定理缩小 n n n 和 m m m,右边的除法式还可以继续用卢卡斯,时间复杂度为 log p n \log_p n logpn。
证明右转 OI Wiki,我不会告诉你我不会证明。
扩展卢卡斯定理
这是在 p p p 不是质数的情况下使用的。主要思想是将 p p p 分解质因数,分别 l u c a s lucas lucas,然后用中国剩余定理合并答案。
例题
很明显,题目要求的是具有小根堆性质的数列。设 d p i dp_i dpi 为 i i i 个数的排列中满足小根堆性质的个数。设 i i i 为根节点,那么有 i − 1 i-1 i−1 个点是子节点。那么从 i − 1 i-1 i−1 个节点中选择 l l l 个节点为左子节点,为 C i − 1 l C_{i-1}^l Ci−1l,那么得出 d p i = C s o n i − 1 s o n ⌊ i 2 ⌋ × d p i × 2 × d p i × 2 + 1 dp_i=C_{son_i-1}^{son_{\lfloor\frac{i}{2}\rfloor}}\times dp_{i\times 2}\times dp_{i\times 2+1} dpi=Csoni−1son⌊2i⌋×dpi×2×dpi×2+1。其中, s o n i son_i soni 表示在二叉树下 i 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]);
}