容斥原理
这里对容斥原理进行简单的总结,容斥原理主要用于求n个数能组成的乘积种类数,从这之中我们就可以引申出容斥原理的很多用法,对于给定数字求组合种类的题目,我们就要想到用lcm去运算,对于给定数求互质/不互质时,我们就要想到对给定的数进行质因数分解。下面给出一些容斥原理的入门题。
容斥原理第一题
HDU-2204- Eddy’s爱好
题意就是给出一个数n,问1-n中有多少个数可以表示为m^k,m,k均为正整数且k>1
由于这里k是大于1的,所以我们想一下哪些k是可以被替代的,例如一个数如果可以被表示为
m
4
m^4
m4,那么他一定可以被表示为
(
m
2
)
2
(m^2)^2
(m2)2,所以我们知道了只要枚举所有质数次幂的组合就可以了,同样当我们枚举2的整数次幂和3的整数次幂都会枚举到6,所以我们需要容斥一下,那么容斥的个数上界是多少呢,我们发现235*7=210>60,而2^60>1e18,所以容斥的个数上界是3,容斥枚举的质数上界为59。
容斥原理第一题代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
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 n,ans;
int i;//全局变量表示当前枚举的容斥是几个数的。
void dfs(int pos,int num,int p)
{
if(p==0)
{
long long tmp=pow(n,1.0/num);
if(pow(tmp,(double)num)>n) tmp--;//注意精度
tmp--;
if(tmp>0)
{
if(i&1) ans+=tmp;
else ans-=tmp;
}
return ;
}
if(pos==17) return;
if(num*prime[pos]<60) dfs(pos+1,num*prime[pos],p-1);
dfs(pos+1,num,p);
return ;
}
int main()
{
while(scanf("%lld",&n)!=EOF)
{
ans=0;
for(i=1;i<=3;i++) dfs(0,1,i);
printf("%lld\n",ans+1);
}
return 0;
}
容斥原理第二题
HDU-3208-Integer’s Power
题意就是定义
p
o
w
e
r
(
x
)
power(x)
power(x)为
x
x
x能被表示为
n
k
n^k
nk中最大的
k
k
k,例如
p
o
w
e
r
(
9
)
=
2
,
p
o
w
e
r
(
32
)
=
5
,
p
o
w
e
r
(
18
)
=
1
power(9)=2,power(32)=5,power(18)=1
power(9)=2,power(32)=5,power(18)=1,给出
l
,
r
l,r
l,r计算
Σ
r
i
=
l
p
o
w
e
r
(
i
)
\underset{i=l}{\overset{r}{\varSigma}}power\left( i \right)
i=lΣrpower(i)
很明显可以将题意转化为
Σ
r
i
=
1
p
o
w
e
r
(
i
)
−
Σ
l
−
1
i
=
1
p
o
w
e
r
(
i
)
\underset{i=1}{\overset{r}{\varSigma}}power\left( i \right) -\underset{i=1}{\overset{l-1}{\varSigma}}power\left( i \right)
i=1Σrpower(i)−i=1Σl−1power(i)
由于这题要求可以被表示的最大次幂,我们就不能像上一题一样容斥,因为某些数表示为
m
4
m^4
m4比表示为
(
m
2
)
2
(m^2)^2
(m2)2更优,所以我们要先算出所有数作为次幂可能的出现次数,由于
2
6
0
>
1
0
1
8
2^60>10^18
260>1018所以我们只需要算到60即可,在预处理出这个数组之后,我们就可以进行容斥,这道题要怎么容斥呢,我们想一下比如我们计算6作为次幂出现的次数为
d
p
[
6
]
dp[6]
dp[6],那么这之中肯定已经包含了
d
p
[
2
]
dp[2]
dp[2]和
d
p
[
3
]
dp[3]
dp[3]中的一部分,相对于2,3,我们肯定选择6,因为他更大,所以我们要让
d
p
[
2
]
−
=
d
p
[
6
]
,
d
p
[
3
]
−
=
d
p
[
6
]
dp[2]-=dp[6],dp[3]-=dp[6]
dp[2]−=dp[6],dp[3]−=dp[6],很明显我们要对某个数的所有因子项减去当前数的出现次数,怎么保证每个数组做贡献的时候是最终状态呢,我们只要逆序去dp就可以了,这样就保证了每个数组去更新其他数组的时候也自己已经是最终状态。另外这题利用
p
o
w
(
x
,
1
/
y
)
pow(x,1/y)
pow(x,1/y)去计算
x
y
\sqrt[y]{x}
yx具有精度问题,所以要在求出之后左右判一下精度问题。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll dp[63];
ll pow_(ll x,int n)
{
ll ans=1;
while(n)
{
if(n&1)
{
double tmp=1.0*INF/ans;
if(x>tmp) return -1;
ans*=x;
}
n>>=1;
if(x>((ll)1<<31)&&n) return -1;
x=x*x;
}
return ans;
}
ll cal(ll x,ll n)//返回精确的\sqrt[y]{x}
{
ll a=(ll)pow(x,1.0/n);
ll qt=pow_(a,n);
ll ql=pow_(a-1,n);
ll qr=pow_(a+1,n);
if(qr!=-1&&qr<=x) return a+1;
else if(qt!=-1&&qt<=x) return a;
else if(ql!=-1&&ql<=x) return a-1;
}
ll solve(ll n)
{
if(n==1) return 1;
int i,j;
memset(dp,0,sizeof(dp));
dp[1]=n;
for(i=2;i<63;i++)
{
dp[i]=cal(n,i)-1;
if(dp[i]==0) break;
}
int k=i;
for(i=k-1;i>0;i--)//逆序DP
{
for(j=1;j<i;j++)
{
if(i%j==0) dp[j]-=dp[i];
}
}
ll ans=0;
for(int i=1;i<k;i++) ans+=1LL*i*dp[i];
return ans;
}
int main()
{
ll l,r;
while(scanf("%lld%lld",&l,&r)!=EOF)
{
if(l==0&&r==0) break;
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}
容斥原理第三题
HDU-1796-How many integers can you find
题意就是给出一个整数n,一个具有m个元素的数组,求出1-n中有多少个数至少能整除m数组中的一个数
这道题就是经典的给出某个数组去组合的问题,只要对当前选中元素取lcm即可,但是要注意这题m数组会出现0,而且算术过程中ans,lcm可能会超过int,最好全程用long long
容斥原理第三题代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 205;
int a[maxn];
int m,i;
long long ans;
long long n;
int gcd_(int a, int b)
{
return b ==0? a : gcd_(b, a % b);
}
int lcm_(int a, int b)
{
return a / gcd_(a, b) * b;
}
void dfs(int pos,int num,int p)
{
if(p==0)
{
if(num==0) return ;//注意0要特判掉
long long tmp=(n-1)/num;
if(i&1) ans+=tmp;
else ans-=tmp;
return ;
}
if(pos==m) return ;
if(lcm_(num,a[pos])<n) dfs(pos+1,lcm_(num,a[pos]),p-1);
dfs(pos+1,num,p);
return ;
}
int main()
{
while(scanf("%lld%d",&n,&m)!=EOF)
{
ans=0;
for(int i=0;i<m;i++) scanf("%d",&a[i]);
for(i=1;i<=m;i++) dfs(0,1,i);
printf("%lld\n",ans);
}
return 0;
}
容斥原理第四题
HDU-2841-Visible Trees
这道题题意就是给出第一象限的n*m个点,求出站在原点可以看见多少个点
将题意稍微转化一下就变成了,求
(
a
,
b
)
(
1
<
=
a
<
=
n
,
1
<
=
b
<
=
m
,
g
c
d
(
a
,
b
)
=
1
)
(a,b)(1<=a<=n,1<=b<=m,gcd(a,b)=1)
(a,b)(1<=a<=n,1<=b<=m,gcd(a,b)=1)的点对数,由于此题n,m范围均为1e5,所以我们只要枚举a,计算1-m范围内有多少个与a互质的数就可以了,可以发现这是个经典问题,所以就解决了。
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
vector<int> v;
int cal(int n,int x)//计算1-n中有多少个与x互质的数
{
int ans=0;
v.clear();
for(int i=2;i*i<=x;i++)
{
if(x%i==0)
{
v.push_back(i);
while(x%i==0) x/=i;
}
}
if(x>1) v.push_back(x);
int sz=v.size();
for(int i=0;i<(1<<sz);i++)
{
int num=0;
int tmp=1;
for(int j=0;j<sz;j++)
{
if(i&(1<<j))
{
num++;
tmp*=v[j];
}
}
tmp=n/tmp;
if(num&1) ans-=tmp;
else ans+=tmp;
}
return ans;
}
int main()
{
int n,m;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
long long ans=0;
for(int i=1;i<=n;i++)
{
ans+=cal(m,i);
}
printf("%lld\n",ans);
}
return 0;
}
容斥原理第五题
ZOJ-2836-Number Puzzle
题意就是给出一个整数m,一个具有n个元素的数组,求出1-m中有多少个数至少能整除n数组中的一个数
这道题就是经典的给出某个数组去组合的问题,只要对当前选中元素取lcm即可
容斥原理第五题代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 205;
int a[maxn];
int m,i;
long long ans;
long long n;
int gcd_(int a, int b)
{
return b ==0? a : gcd_(b, a % b);
}
int lcm_(int a, int b)
{
return a / gcd_(a, b) * b;
}
void dfs(int pos,int num,int p)
{
if(p==0)
{
if(num==0) return ;
long long tmp=n/num;
if(i&1) ans+=tmp;
else ans-=tmp;
return ;
}
if(pos==m) return ;
if(lcm_(num,a[pos])<=n) dfs(pos+1,lcm_(num,a[pos]),p-1);
dfs(pos+1,num,p);
return ;
}
int main()
{
while(scanf("%d%lld",&m,&n)!=EOF)
{
ans=0;
for(int i=0;i<m;i++) scanf("%d",&a[i]);
for(i=1;i<=m;i++) dfs(0,1,i);
printf("%lld\n",ans);
}
return 0;
}
容斥原理第六题
ZOJ-3233-Lucky Number
题意就是给你一堆幸运数,在给你一堆不幸运数,要求一个区间内的数有多少个数满足至少是一个幸运数的倍数,而且不是每个不幸运数的倍数。
我们可以把这两个问题分开来看,首先我们可以利用容斥求出所有至少是一个幸运数的倍数的数的个数,我们只要在这个过程中给把满足第二种条件的保留就可以了,把第二种条件进一步表达,就变为了不能整除所有不幸运数的
l
c
m
lcm
lcm,而如果我们当前枚举的
l
c
m
lcm
lcm为
n
u
m
num
num,所有不幸运数的
l
c
m
=
u
m
lcm=um
lcm=um,我们怎样去掉这之中作为
u
m
um
um的倍数的情况呢我们只要减去
n
/
(
l
c
m
(
n
u
m
,
u
m
)
)
n/(lcm(num,um))
n/(lcm(num,um))即可,所以这道题就做完了,这道题坑点有lcm可能超过longlong,这种时候我们发现一定满足第二种条件,所以只要只计算第一种条件的数量即可。
(另外,ZOJRE可能会返回wa,因为这个-6 )=_=||
容斥原理第六题代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1005;
ll a[maxn];
ll b[maxn];
int n,m,i,en,flag;
ll LCM;
long long ans;
ll gcd_(ll a, ll b)
{
return b ==0? a : gcd_(b, a % b);
}
ll lcm_(ll a, ll b)
{
return a / gcd_(a, b) * b;
}
void dfs(int pos,ll num,int p,ll x)
{
if(p==0)
{
long long tmp=x/num;
if(flag==0) tmp=tmp-x/lcm_(num,LCM);
if(i&1) ans+=tmp;
else ans-=tmp;
return ;
}
if(pos==en) return ;
if(lcm_(num,a[pos])<=x) dfs(pos+1,lcm_(num,a[pos]),p-1,x);
dfs(pos+1,num,p,x);
return ;
}
int main()
{
ll x,y;
while(scanf("%d%d%lld%lld",&m,&n,&x,&y)!=EOF)
{
flag=0;
LCM=1;
if(m==0&&n==0&&x==0&&y==0) break;
ll ans1=0,ans2=0;
for(int i=0;i<m;i++) scanf("%lld",&a[i]);
for(int i=0;i<n;i++)
{
scanf("%lld",&b[i]);
LCM=lcm_(LCM,b[i]);
}
if(LCM<0)//爆longlong所以不需要统计第二种情况
{
flag=1;
en=m;
ans=0;
for(i=1;i<=m;i++) dfs(0,1,i,y);
ans1+=ans;
ans=0;
for(i=1;i<=m;i++) dfs(0,1,i,x-1);
ans1-=ans;
printf("%lld\n",ans1);
}
else
{
en=m;
ans=0;
for(i=1;i<=m;i++) dfs(0,1,i,y);
ans1+=ans;
ans=0;
for(i=1;i<=m;i++) dfs(0,1,i,x-1);
ans1-=ans;
printf("%lld\n",ans1);
}
}
return 0;
}
容斥原理第七题
URAL-1091-Tmutarakan Exams
将题意简化一下就是给你一个数s,要用1-s构造一个长度为序列保证n个数的最大公约数为1,且n个数均不相同。
我们直接构造不太好想,于是我们想如何构造出不满足情况的序列,那么就是最大公约数>=2.由于这道题s,k均<=50.所以我们可以枚举gcd为2-S,同样如果一个序列的gcd为4,那么他一定被gcd为2的计算过,所以我们还是只需要枚举所有质数的组合即可,当枚举gcd为2时,一共n个数,一共s/2种选择,那么C(s/2,n)即是最终答案,以此类推,容斥一下就解决了。
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 105;
long long c[maxn][maxn];
int pri[20]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,};
void getC()
{
c[0][0]=1;
for(int i=1;i<=100;i++)
{
c[i][0]=c[i][i]=1;
for(int j=0;j<i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1]);
}
}
int main()
{
int k,s;
scanf("%d%d",&k,&s);
getC();
ll ans=0;
for(int i=1;i<(1<<15);i++)
{
ll num=1;
int cnt=0;
for(int j=0;j<15;j++)
{
if(i&(1<<j))
{
cnt++;
num*=pri[j];
}
}
ll tmp=0;
if(s/num>=k) tmp=c[s/num][k];
if(cnt&1) ans+=tmp;
else ans-=tmp;
}
printf("%lld\n",min(ans,(ll)10000));
return 0;
}
容斥定理第八题
POJ-1091-跳蚤
题意简化之后就是给你一个数m,一个数n,让你构造一个长度为n+1的序列,第n+1个数为m,保证这n+1个数<=m,而且这n+1个数的最大公约数为1。
我们无法直接计算最大公约数为1的组合数,所以我们只要计算出所有最大公约数不为1的再用总排列数减去即可,最大公约数不为1的时候只能为m的因子,所以我们只要对m的质因子容斥一下,求一下每种质因子的组合作为最大公约数的情况就可以了。
容斥定理第八题代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
ll pow_(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
vector<int> v;
int main()
{
ll n,m;
scanf("%lld%lld",&n,&m);
ll ans=pow_(m,n);
ll ans2=0;
ll tmp=m;
for(ll i=2;i*i<=m;i++)
{
if(m%i==0)
{
v.push_back(i);
while(m%i==0) m/=i;
}
}
if(m>1) v.push_back(m);
for(int i=1;i<(1<<v.size());i++)
{
ll num=1;
int cnt=0;
for(int j=0;j<v.size();j++)
{
if(i&(1<<j))
{
cnt++;
num=num*v[j];
}
}
if(cnt&1)
{
ans2+=pow_(tmp/num,n);
}
else
{
ans2-=pow_(tmp/num,n);
}
}
ans=ans-ans2;
printf("%lld\n",ans);
return 0;
}
容斥原理第九题
HDU-4135-Co-prime
求A-B之间与N互质的数的个数,我们首先对N分解质因数,再对其所有因子进行容斥,最后能得到所有与N不互质的数的个数,最后用n减去这个个数,就是与n互质的数的个数。
容斥原理第九题代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
vector<int> v;
ll cal(ll n,ll x)
{
ll ans=0;
v.clear();
for(ll i=2;i*i<=x;i++)
{
if(x%i==0)
{
v.push_back(i);
while(x%i==0) x/=i;
}
}
if(x>1) v.push_back(x);
int sz=v.size();
for(int i=0;i<(1<<sz);i++)
{
int num=0;
ll tmp=1;
for(int j=0;j<sz;j++)
{
if(i&(1<<j))
{
num++;
tmp*=v[j];
}
}
tmp=n/tmp;
if(num&1) ans-=tmp;
else ans+=tmp;
}
return ans;
}
int main()
{
int t;
scanf("%d",&t);
int cnt=1;
while(t--)
{
ll a,b,n;
scanf("%lld%lld%lld",&a,&b,&n);
ll ans=cal(b,n)-cal(a-1,n);
printf("Case #%d: %lld\n",cnt++,ans);
}
return 0;
}
容斥原理第十题
G. Spare Tire
题意就是给出a(n)的计算方法为
a
n
=
0
n
=
0
a_n=0\ \ \ n=0
an=0 n=0
a
n
=
2
n
=
1
a_n=2\ \ \ n=1
an=2 n=1
a
n
=
3
a
n
−
1
−
a
n
−
2
2
+
n
+
1
n
>
1
a_n=\frac{3a_{n-1}-a_{n-2}}{2}+\ n+1\ \ \ \ n>1
an=23an−1−an−2+ n+1 n>1
\ \ \ \ \
之后给出数字n,m,求1-n种与m互质的数的a计算方法之和
通过打表可以发现
a
n
=
n
∗
(
n
+
1
)
a_n=n*\left( n+1 \right)
an=n∗(n+1)
所以我们只要能够计算出1-n中所有与m互质的数就可以了,我们发现容斥的某个经典问题就是求1-n中与m互质的数的个数,过程中我们可以算出作为某个组合出的因子的所有倍数的出现次数,我们当时的计算方法是如果这个因子为tmp,那么作为他的倍数的出现次数就是
t
i
m
e
=
n
/
t
m
p
time=n/tmp
time=n/tmp,那么换到这里,我们想要计算的就是
Σ
t
i
m
e
i
=
1
(
i
∗
t
m
p
)
∗
(
i
∗
t
m
p
+
1
)
\underset{i=1}{\overset{time}{\varSigma}}\left( i*tmp \right) *\left( i*tmp+1 \right)
i=1Σtime(i∗tmp)∗(i∗tmp+1)
如果我们设
t
=
i
∗
t
m
p
t=i*tmp
t=i∗tmp,上面的式子就变为
Σ
t
i
m
e
i
=
1
t
2
+
t
\underset{i=1}{\overset{time}{\varSigma}}t^2+t
i=1Σtimet2+t
=
t
i
m
e
×
(
t
i
m
e
+
1
)
×
(
2
×
t
i
m
e
+
1
)
6
×
t
m
p
2
+
t
i
m
e
×
(
t
i
m
e
+
1
)
2
×
t
m
p
=\frac{time\times \left( time+1 \right) \times \left( 2\times time+1 \right)}{6}\times tmp^2+\frac{time\times \left( time+1 \right)}{2}\times tmp
=6time×(time+1)×(2×time+1)×tmp2+2time×(time+1)×tmp
于是这道题就解决了,时间复杂度O(能过)
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int Mod = 1e9+7;
vector<int> v;
ll pow_(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=(ans*a)%Mod;
a=(a*a)%Mod;
b>>=1;
}
return ans;
}
ll inv(ll x)
{
return pow_(x,Mod-2);
}
ll tmp;
ll cal2(ll x,ll n)
{
ll ans=1;
ans=(ans*x*x)%Mod;
ans=(ans*tmp)%Mod;
ans=(ans*n)%Mod;
ans=(ans*(n+1))%Mod;
ans=(ans*(2*n+1))%Mod;
ans=(ans+x*((n*(n+1)%Mod)*inv(2)%Mod)%Mod)%Mod;
return ans;
}
ll cal(ll n,ll x)
{
ll ans=0;
v.clear();
for(ll i=2;i*i<=x;i++)
{
if(x%i==0)
{
v.push_back(i);
while(x%i==0) x/=i;
}
}
if(x>1) v.push_back(x);
int sz=v.size();
for(int i=1;i<(1<<sz);i++)
{
int num=0;
ll tmp=1;
for(int j=0;j<sz;j++)
{
if(i&(1<<j))
{
num++;
tmp*=v[j];
}
}
if(num&1) ans=(ans+cal2(tmp,n/tmp))%Mod;
else ans=(ans-cal2(tmp,n/tmp)%Mod+Mod)%Mod;
}
return (cal2(1,n)-ans%Mod+Mod)%Mod;
}
int main()
{
ll n,m;
tmp=inv(6);
while(scanf("%lld%lld",&n,&m)!=EOF)
{
ll ans=cal(n,m);
printf("%lld\n",ans);
}
return 0;
}