组合计数问题中容斥原理的应用
容斥原理作为数学中的一个重要定理,在ACM当中也有重要的应用,可以用于解决组合计数问题,概率论问题,数论问题。
具体参见2013 成都七中 王迪 《浅析容斥原理》
容斥原理当中奇数个集合为正,偶数个集合为负。
其核心思想是:把重复的扣掉,再把扣多的加回来。
初始化时会用到的公式:
C(m,0)=C(m,m)=1 C(n,k)+C(n,k+1)=C(n+1,k+1)
1.容斥原理在组合计数当中的应用
刘汝佳《训练指南》107页的例题3 很经典的一道题目
链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2906
具体解法,参照训练指南,此处不在多论述,是一道非常经典的用二进制记录容斥原理的题目
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn=500+10; const int mod=1000007; int C[maxn][maxn]; int main() { memset(C,0,sizeof(C)); //初始化 C[0][0]=1; for(int i=0;i<=500;i++) { C[i][0]=C[i][i]=1; //千万不能忘记边界条件 for(int j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; } int t; scanf("%d",&t); for(int cas=1;cas<=t;cas++) { int n,m,k,sum=0; scanf("%d%d%d",&n,&m,&k); for(int s=0;s<16;s++) //枚举所有16种搭配方式 { int r=n,c=m,b=0; //b用来统计集合的个数,r和c是可以放置的行列数 if(s&1) //第一行没有石头,r减1 { r--; b++; } if(s&2) //最后一行没有石头,r减1 { r--; b++; } if(s&4) //第一列没有石头,c减1 { c--; b++; } if(s&8) //最后一列没有石头,c减1 { c--; b++; } if(b&1) sum=(sum+mod-C[r*c][k])%mod; //奇数的条件,做减法(容斥原理) else sum=(sum+C[r*c][k])%mod; //偶数的条件,做加法(容斥原理) } printf("Case %d: %d\n",cas,sum); } return 0; }
容斥原理的应用:
用二进制标记容斥原理,奇数的地方加,偶数的减
链接:http://acm.hdu.edu.cn/showproblem.php?pid=4336
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int maxn=20+5; double a[maxn]; int main() { int n; while(cin>>n) { for(int i=1;i<=n;i++) cin>>a[i]; double xh=0.0; for(int i=1;i<(1<<n);i++) //二进制标记容斥原理 { double sum=0.0; int odd=0; for(int j=0;j<n;j++) if((1<<j)&i) { odd++; sum+=a[j+1]; } if(odd&1) //奇数加偶数减 xh+=1.0/sum; else xh-=1.0/sum; } printf("%.6lf\n",xh); } return 0; }
容斥原理在乘除问题上的应用:
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1796
求[1,r)内有因子出现在集合中的数的个数。题目要求的数要么含有一个集合中的因子,要么是两个,要么是三个..要么是m个,而含两个集合中因子的数在计算含有一个集合中的因子,被重复计算了,三个时候也在计算二个的时候被重复计算,所以需要用到容斥原理。2^m枚举集合中元素,然后计算出最小公倍数,n/LCM就是1..n中含我们枚举的因子的数的个数对于求出来的数根据枚举到素因子个数奇数加偶数加。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int maxn=20; int arr[maxn]; int n,m,cnt,ans; __int64 gcd(__int64 a,__int64 b) { if(b==0) return a; else return gcd(b,a%b); } void dfs(__int64 i,__int64 a,__int64 b) { for(;i<=m;i++) { if(arr[i]) { cnt=arr[i]*a/gcd(arr[i],a); ans+=b*(n/cnt); dfs(i+1,cnt,-b); } } return; } int main() { while(cin>>n>>m) { for(int i=1;i<=m;i++) scanf("%d",&arr[i]); ans=0; n--; dfs(1,1,1); cout<<ans<<endl; } return 0; }
链接:http://acm.hdu.edu.cn/showproblem.php?pid=4135
题解:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int maxn=70; long long prime[maxn]; long long solve(long long num,int m) { long long flag,temp,ans=0,i,j; for(i=1;i<(long long)(1<<m);i++) //用二进制来1,0来表示第几个素因子是否被用到,如m=3,三个因子是2,3,5,则i=3时二进制是011,表示第2、3个因子被用到 { flag=0,temp=1; for(j=0;j<m;j++) if(i&(long long)(1<<j)) //判断第几个因子目前被用到 { flag++; temp*=prime[j]; } if(flag&1) //容斥原理,奇加偶减 ans+=num/temp; else ans-=num/temp; } return ans; } int main() { int t,m; long long a,b,n,i; scanf("%d",&t); for(int cas=1;cas<=t;cas++) { cin>>a>>b>>n; m=0; for( i=2;i*i<=n;i++) //对n进行素因子分解 if(n&&n%i==0) { prime[m++]=i; while(n&&n%i==0) n=n/i; } if(n>1) prime[m++]=n; printf("Case #%d: %I64d\n",cas,b-solve(b,m)-(a-1-solve(a-1,m))); } return 0; }