知识点 - 容斥原理 鸽巢原理 乘法原理
解决问题类型:
容斥:算重了。
- 首位大于1,末尾小于8的排列数
- 长度为n的由 (0, 1, 2) 构成,且0,1,2至少出现一次的子序列个数
- n元一次方程整数解个数。
- 给定区间互质数对个数
- 给n个,问给定区间中是某个 a i a_{i} ai的倍数的个数
- 符合给定规则的串的个数
- 从一个格子走到另一个格子的方案数
- 互质四元组的个数
- harmonic triplets的个数
- 错排问题
鸽巢:
题目给了n,考虑n+1的情况。
结论
容斥公式
(1)设
A
1
,
A
2
,
⋅
⋅
⋅
,
A
n
A_{1},A_{2}, \cdot \cdot \cdot ,A_{n}
A1,A2,⋅⋅⋅,An是集合
S
S
S 的子集,表示以集合
S
S
S 代表可能发生的事件中的
n
n
n 个子事件,
∣
A
i
∣
\left| A_{i} \right|
∣Ai∣表示子事件
A
i
A_{i}
Ai发生的个数
(
0
≤
i
≤
n
)
\left( 0 \leq i \leq n \right)
(0≤i≤n),则有
∣
A
1
∪
A
2
∪
A
3
∪
⋅
⋅
⋅
∪
A
n
∣
=
∑
i
=
1
n
∣
A
i
∣
−
∑
1
≤
i
<
j
≤
n
∣
A
i
∩
A
j
∣
+
⋅
⋅
⋅
+
(
−
1
)
n
−
1
∣
A
1
∩
A
2
∩
A
3
∩
⋅
⋅
⋅
∩
A
n
∣
\left| A_{1} \cup A_{2} \cup A_{3} \cup \cdot \cdot \cdot \cup A_{n} \right| = \sum_{i = 1}^{n}\left| A_{i} \right| - \sum_{1 \leq i < j \leq n}^{}\left| A_{i} \cap A_{j} \right| + \cdot \cdot \cdot + \left( - 1 \right)^{n - 1}\left| A_{1} \cap A_{2} \cap A_{3} \cap \cdot \cdot \cdot \cap A_{n} \right|
∣A1∪A2∪A3∪⋅⋅⋅∪An∣=i=1∑n∣Ai∣−1≤i<j≤n∑∣Ai∩Aj∣+⋅⋅⋅+(−1)n−1∣A1∩A2∩A3∩⋅⋅⋅∩An∣
压缩版:
∣ ⋃ i = 1 n A i ∣ = ∑ ∅ ≠ J ⊆ 1 , 2 , … , n ( − 1 ) ∣ J ∣ − 1 ∣ ⋂ j ∈ J A j ∣ \left|\bigcup_{i=1}^n A_i \right| = \sum_{\emptyset \neq J\subseteq \\{1,2,\ldots ,n\\}} (-1)^{|J|-1}{\Biggl |}\bigcap_{j\in J}A_{j}{\Biggr |} ∣∣∣∣∣i=1⋃nAi∣∣∣∣∣=∅̸=J⊆1,2,…,n∑(−1)∣J∣−1∣∣∣∣∣j∈J⋂Aj∣∣∣∣∣
概率版:
KaTeX parse error: Expected '}', got '\cal' at position 2: {\̲c̲a̲l̲ ̲P}(A)代表A事件发生的概率。
KaTeX parse error: Expected '}', got '\cal' at position 3: {\̲c̲a̲l̲ ̲P} \left(\bigcu…
补集公式:
(A上面有横线)
∣
A
1
‾
∩
A
2
‾
∩
A
3
‾
∩
⋅
⋅
⋅
∩
A
n
‾
∣
=
S
−
∣
A
1
∪
A
2
∪
A
3
∪
⋅
⋅
⋅
∪
A
n
∣
\left| \overset{\overline{}}{A_{1}} \cap \overset{\overline{}}{A_{2}} \cap \overset{\overline{}}{A_{3}} \cap \cdot \cdot \cdot \cap \overset{\overline{}}{A_{n}} \right| = S - \left| A_{1} \cup A_{2} \cup A_{3} \cup \cdot \cdot \cdot \cup A_{n} \right|
∣∣∣A1∩A2∩A3∩⋅⋅⋅∩An∣∣∣=S−∣A1∪A2∪A3∪⋅⋅⋅∪An∣
补集版
∣ ⋂ i = 1 n A i ‾ ∣ = ∑ m = 0 n ( − 1 ) m ∑ ∣ X ∣ = m ∣ ⋂ i ∈ X A i ∣ \left|\bigcap_{i=1}^n \overline{A_i}\right|=\sum_{m=0}^n (-1)^m \sum_{|X|=m} \left|\bigcap_{i\in X} A_{i}\right| ∣∣∣∣∣i=1⋂nAi∣∣∣∣∣=m=0∑n(−1)m∣X∣=m∑∣∣∣∣∣i∈X⋂Ai∣∣∣∣∣
集合大小为r版
∣ ⋃ ∣ B ∣ = r [ ⋂ i ∈ B A i ∩ ⋂ j ̸ ∈ B A j ‾ ] ∣ = ∑ m = r n ( − 1 ) m − r ( m r ) ∑ ∣ X ∣ = m ∣ ⋂ i ∈ X A i ∣ \left|\bigcup_{|B|=r}\left[\bigcap_{i \in B} A_i \cap \bigcap_{j \not\in B} \overline{A_j}\right]\right|=\sum_{m=r}^n (-1)^{m-r}\dbinom{m}{r} \sum_{|X|=m} \left|\bigcap_{i \in X} A_{i}\right| ∣∣∣∣∣∣∣B∣=r⋃⎣⎡i∈B⋂Ai∩j̸∈B⋂Aj⎦⎤∣∣∣∣∣∣=m=r∑n(−1)m−r(rm)∣X∣=m∑∣∣∣∣∣i∈X⋂Ai∣∣∣∣∣
容斥问题
错排问题
(1)设
D
n
D_{n}
Dn表示
1
,
2
,
3
,
⋅
⋅
⋅
,
n
1,2,3, \cdot \cdot \cdot ,n
1,2,3,⋅⋅⋅,n这n个数的一个排列的错排个数,有
D
n
=
n
!
[
1
−
1
1
!
+
1
2
!
−
1
3
!
+
⋅
⋅
⋅
+
(
−
1
)
n
1
n
!
]
,
D
1
=
0
,
D
2
=
1
D_{n} = n!\left\lbrack 1 - \frac{1}{1!} + \frac{1}{2!} - \frac{1}{3!} + \cdot \cdot \cdot + \left( - 1 \right)^{n}\frac{1}{n!} \right\rbrack,D_{1} = 0,D_{2} = 1
Dn=n![1−1!1+2!1−3!1+⋅⋅⋅+(−1)nn!1],D1=0,D2=1
D n = ( n − 1 ) [ D n − 1 + D n − 2 ] , n > 2 D_n=(n-1)[D_{n-1}+D_{n-2}],n>2 Dn=(n−1)[Dn−1+Dn−2],n>2
D n = n D n − 1 + ( − 1 ) n D_n=nD_{n-1}+(-1)^n Dn=nDn−1+(−1)n
带有禁位的错排问题
(1)n个元素
a
1
,
a
2
,
a
3
,
⋅
⋅
⋅
,
a
n
a_{1},a_{2},a_{3}, \cdot \cdot \cdot ,a_{n}
a1,a2,a3,⋅⋅⋅,an带有禁位
X
1
,
X
2
,
X
3
,
⋅
⋅
⋅
,
X
n
X_{1},X_{2},X_{3}, \cdot \cdot \cdot ,X_{n}
X1,X2,X3,⋅⋅⋅,Xn的错排数为
P
(
X
1
,
X
2
,
X
3
,
⋅
⋅
⋅
,
X
n
)
=
n
!
−
r
1
(
n
−
1
)
!
+
r
2
(
n
−
2
)
!
−
⋅
⋅
⋅
+
(
−
1
)
k
r
k
(
n
−
k
)
!
+
⋅
⋅
⋅
+
(
−
1
)
n
r
n
P\left( X_{1},X_{2},X_{3}, \cdot \cdot \cdot ,X_{n} \right) = n! - r_{1}\left( n - 1 \right)! + r_{2}\left( n - 2 \right)! - \cdot \cdot \cdot + \left( - 1 \right)^{k}r_{k}\left( n - k \right)! + \cdot \cdot \cdot + \left( - 1 \right)^{n}r_{n}
P(X1,X2,X3,⋅⋅⋅,Xn)=n!−r1(n−1)!+r2(n−2)!−⋅⋅⋅+(−1)krk(n−k)!+⋅⋅⋅+(−1)nrn
式中
r
k
r_{k}
rk表示有
k
k
k 个元素在禁位上的个数
复杂度:
例题
HDU2204 容斥:给你一个正整数N,确定在1到N之间有多少个可以表示成M^K(K>1)的数。
分析:小于 N N N的 M k M^k Mk的个数为 ⌊ N k ⌋ \lfloor \sqrt[k]{N}\rfloor ⌊kN⌋。枚举所有的k的话。显然有东西算重了。(比如1,我们可以每个k都不统计,最后加1)
首先,如果k不是质数 e . g . M k = M p 1 p 2 e.g.M^k=M^{p1p2} e.g.Mk=Mp1p2,则 M k M^k Mk就以 ( M p 1 ) p 2 (M^{p1})^{p2} (Mp1)p2的形式被 ⌊ N p 2 ⌋ \lfloor \sqrt[p_2]{N}\rfloor ⌊p2N⌋统计过了。于是所有和数都不用统计。
其次,我们发现上面的例子里 M k M^k Mk被 ⌊ N p 1 ⌋ \lfloor \sqrt[p_1]{N}\rfloor ⌊p1N⌋ ⌊ N p 2 ⌋ \lfloor \sqrt[p_2]{N}\rfloor ⌊p2N⌋都统计了。于是我们做容斥,K包含1个质数-K包含2个质数+3个…
最后,注意到 2 60 > 1 0 18 2^{60}>10^{18} 260>1018所以容斥到三个质数即可,最大的质数取到59。
hdu1205鸽巢:显然的想法题,但不会用鸽巢原理证明。
hdu5525 约数乘积:
n
=
p
1
e
1
⋅
p
2
e
2
⋯
p
k
e
k
n=p_1^{e_1} \cdot p_2^{e_2} \cdots p_k^{e_k}
n=p1e1⋅p2e2⋯pkek对于每个质数,它被乘入答案的次数为
Σ
i
=
1
i
≤
e
i
⋅
d
(
n
p
e
)
\Sigma_{i=1}^{i\leq e}i\cdot d(\frac{n}{p^e})
Σi=1i≤ei⋅d(pen),d(n)为因数个数。
∏
i
=
1
i
≤
k
p
i
(
Σ
j
=
1
j
≤
e
i
j
⋅
d
(
n
p
i
e
i
)
)
\prod_{i=1}^{i\leq k}p_i^{(\Sigma_{j=1}^{j\leq e_i}j\cdot d(\frac{n}{p_i^{e_i}}))}
i=1∏i≤kpi(Σj=1j≤eij⋅d(piein))
注意指数上要模p-1,所以不能用除法。对于/2有
2
∣
(
n
+
1
)
n
2|(n+1)n
2∣(n+1)n 直接把2乘到模数上。对于
(
1
+
e
i
)
(1+e_i)
(1+ei)维护前后缀积。
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int prime[20]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59};
long long ans,n;
int i;
void dfs(int j,int num,int p){
if(p==0){
long long t=pow(n,1.0/num);
if(pow(t,0.0+num)>n) t--;
t--;
if(t>0)
//i为奇数则加,否则减
ans+=t*(i&1?1:-1);
return ;
}
if(j>=17)
return;//超出60以内素数,不予考虑
if(num*prime[j]<60) //仍在范围内,继续搜
dfs(j+1,num*prime[j],p-1);
dfs(j+1,num,p);
}
int main(){
while(scanf("%I64d",&n)!=EOF){
ans=0;
for(i=1;i<=3;i++)
dfs(0,1,i);
printf("%I64d\n",ans+1);
}
return 0;
}
//T2
const int N=2e5+10,M=4e6+10,inf=2147483647;
const ll INF=1e18+10,mod=1e9+7;
/// 数组大小
int prime(int n)
{
if(n<=1)
return 0;
if(n==2)
return 1;
if(n%2==0)
return 0;
int k, upperBound=n/2;
for(k=3; k<=upperBound; k+=2)
{
upperBound=n/k;
if(n%k==0)
return 0;
}
return 1;
}
vector<int>p;
int si[N];
void init()
{
for(int i=2;i<=100000;i++)
if(prime(i))
p.push_back(i);
}
ll quick(ll a,ll b,ll c)
{
ll ans=1;
while(b)
{
if(b&1)ans=(ans*a)%c;
b>>=1;
a=(a*a)%c;
}
return ans;
}
int main()
{
init();
int n;
while(~scanf("%d",&n))
{
memset(si,0,sizeof(si));
for(int i=1;i<=n;i++)
{
int z;
int x=i;
scanf("%d",&z);
for(int j=0;j<p.size();j++)
{
if(1LL*p[j]*p[j]>x)break;
if(x==1)break;
while(x%p[j]==0)
{
si[j]+=z;
si[j]=(si[j])%(2*(mod-1));
x/=p[j];
}
}
if(x!=1)
{
int pos=lower_bound(p.begin(),p.end(),x)-p.begin();
si[pos]+=z;
}
}
ll sum=1;
for(int i=0;i<p.size();i++)
{
sum*=(si[i]+1);
sum%=(2*(mod-1));
}
//cout<<sum<<endl;
ll ans=1;
for(int i=0;i<p.size();i++)
{
ans=(ans*quick(p[i],((sum*si[i])%(2*(mod-1)))/2,mod))%(mod);
}
printf("%lld\n",ans);
}
return 0;
}