鸽巢原理和容斥原理小结
一、鸽巢原理
内容回顾:
1、若有n个笼子和n+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少2只鸽子。
2、若有n个笼子和kn+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少k+1只鸽子。
鸽巢原理主要在于能否抽象出它的模型,同时在应用其中,例如:
1.如果将1,2……10随机地摆放一圈,则必有相邻的三个数之和至少是17。
2.证明有理数a/b展开的十进制小数是有限小数或是循环小数。
以上都是可以由鸽巢原理得到。
POJ2356 Find a multiple
这题的意思是给你n个数,让你取其中的几个之和使其是n的倍数。
这是鸽巢原理的一个应用,可以先将给出的n个值a1,a2,a3...an,取前i项和sum[i]。。。再将各项sum[i]%n。如果有sum[i]=0,则可以输出前i项了。
但如果没有sum[i]=0的话,则就有了n个介于[1~n-1]的值,根据鸽巢原理,则里面必有两项相等,那该两项相减得到的值必然为n的倍数,所以只要输出该两项之间的ai和后一项的ai值就行了。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<cmath> #include<set> #include<vector> #include<stack> #define mem(a,b) memset(a,b,sizeof(a)) #define FOR(a,b,i) for(i=a;i<=b;++i) #define For(a,b,i) for(i=a;i<b;++i) #define N 1000000007 using namespace std; inline void RD(int &ret) { char c; do { c=getchar(); } while(c<'0'||c>'9'); ret=c-'0'; while((c=getchar())>='0'&&c<='9') { ret=ret*10+(c-'0'); } } inline void OT(int a) { if(a>=10) { OT(a/10); } putchar(a%10+'0'); } int main() { int i,n,a[10001],sum[10001],f,g,p,q,j; RD(n); mem(sum,0); FOR(1,n,i) { RD(a[i]); sum[i]=sum[i-1]+a[i]; } FOR(1,n,i) { sum[i]%=n; } f=0; FOR(1,n,i) { if(sum[i]==0) { printf("%d\n",i); FOR(1,i,j) { OT(a[j]); printf("\n"); } f=1; break; } } if(f==0) { g=0; FOR(1,n,i) { FOR(1,n,j) { if(i==j) { continue; } if(sum[i]==sum[j]) { p=i; q=j; g=1; break; } } if(g==1) { break; } } printf("%d\n",q-p); FOR(p+1,q,i) { OT(a[i]); printf("\n"); } } return 0; }
POJ3370 Halloween treats
这题和上题基本上没有太大的差别,这题需要的是从m个邻居能给的糖果数ai中找到几项和为小孩个数n的倍数,然后输出邻居的编号。但这题的数据量比较大,如果还是用上面的两个for的话会超时,所以需要多一个数组进行标记两个sum[i]是否相同。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<cmath> #include<set> #include<vector> #include<stack> #define mem(a,b) memset(a,b,sizeof(a)) #define FOR(a,b,i) for(i=a;i<=b;++i) #define For(a,b,i) for(i=a;i<b;++i) #define N 1000000007 using namespace std; inline void RD(int &ret) { char c; do { c=getchar(); } while(c<'0'||c>'9'); ret=c-'0'; while((c=getchar())>='0'&&c<='9') { ret=ret*10+(c-'0'); } } inline void OT(int a) { if(a>=10) { OT(a/10); } putchar(a%10+'0'); } int a[100001],sum[100001],g[100001]; int main() { int i,n,j,c; while(1) { RD(c); RD(n); if(c==0&&n==0) { break; } mem(sum,0); mem(g,0); FOR(1,n,i) { RD(a[i]); sum[i]=(sum[i-1]+a[i])%c; } FOR(1,n,i) { if(sum[i]==0) { OT(1); FOR(2,i,j) { printf(" %d",j); } printf("\n"); break; } else { if(g[sum[i]]) { OT(g[sum[i]]+1); FOR(g[sum[i]]+2,i,j) { printf(" %d",j); } printf("\n"); break; } } g[sum[i]]=i; } } return 0; }
鸽巢原理整理完毕,其它的就是将鸽巢原理变形存在于各种题型中。上面两题只是基本运用。。。。
二、容斥原理:
内容回顾:
在计数时,必须注意无一重复,无一遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。
相比鸽巢原理,容斥原理与题目的结合更多一些,难题还待攻克。。
HDU1695 GCD
这题的题意是给两个范围[a,b],[c,d],取出x和y,使gcd(x,y)=k有多少种情况,由于a=c=1(不知道设立成变量有啥用=。=)这样的话,我们先考虑k=0时,情况数为0;
因为gcd(x,y)=k,所以x/k和y/k为互质数,所以我们先要求出b以内所以互质数情况,可以用欧拉函数来求,但要把前一值的欧拉函数加到后一值。但b~d之间的互质数情况就不是很好求了,需要运用容斥原理,将n分解质因数,那么所求区间内与某个质因数不互质的个数就是n / r(r为质因子),那总的不互质数量就可以由容斥原理得到了。再用总的减去就行了。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<cmath> #include<set> #include<vector> #include<stack> #define mem(a,b) memset(a,b,sizeof(a)) #define FOR(a,b,i) for(i=a;i<=b;++i) #define For(a,b,i) for(i=a;i<b;++i) #define N 1000000007 using namespace std; inline void RD(int &ret) { char c; do { c=getchar(); } while(c<'0'||c>'9'); ret=c-'0'; while((c=getchar())>='0'&&c<='9') { ret=ret*10+(c-'0'); } } inline void OT(int a) { if(a>=10) { OT(a/10); } putchar(a%10+'0'); } __int64 phi[100001]; int pri[100001][11],c[100001]; void eular()//欧拉函数和质因数分解 { int i,j; phi[1]=1; mem(c,0); FOR(2,100000,i) { if(!phi[i]) { for(j=i; j<=100000; j+=i) { if(!phi[j]) { phi[j]=j; } phi[j]-=phi[j]/i; pri[j][c[j]++]=i; } } phi[i]+=phi[i-1]; } } __int64 inex(int x,int y,int t)//容斥原理 { __int64 ans=0; int i; For(x,c[t],i) { ans+=y/pri[t][i]-inex(i+1,y/pri[t][i],t); } return ans; } int main() { eular(); int t,cas=0,i,a,b,c,d,k; __int64 sum; RD(t); while(t--) { cas++; RD(a); RD(b); RD(c); RD(d); RD(k); printf("Case %d: ",cas); if(k==0) { sum=0; } else { if(b>d) { swap(b,d); } b/=k; d/=k; sum=phi[b];//前b的互质数数量就是得到的欧拉函数值 FOR(b+1,d,i) { sum+=b-inex(0,b,i); } } printf("%I64d\n",sum); } return 0; }
POJ3695&HDU2461 Rectangles
这题相比上题就好理解的多了,但是关键在于实现,这题给你n个正方形的左下角和右上角的坐标,并且有m个问题,询问你num个指定的正方形的覆盖面积是多少。一般以前看到这里题目,一般就是线段树加离散,但是理解了容斥原理后,正方形的覆盖面积可以为:单个正方形的和-两个正方形相交面积和+三个正方形相交面积和-......但最后都没写出来,感觉有点问题,最后用矩形切割的方法过了。。。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<cmath> #include<set> #include<vector> #include<stack> #define mem(a,b) memset(a,b,sizeof(a)) #define FOR(a,b,i) for(i=a;i<=b;++i) #define For(a,b,i) for(i=a;i<b;++i) #define N 1000000007 using namespace std; inline void RD(int &ret) { char c; do { c=getchar(); } while(c<'0'||c>'9'); ret=c-'0'; while((c=getchar())>='0'&&c<='9') { ret=ret*10+(c-'0'); } } inline void OT(int a) { if(a>=10) { OT(a/10); } putchar(a%10+'0'); } struct xl { int x,y; } p[22],q[22]; int num,ans; int a[22]; void inex(int px,int py,int qx,int qy,int id)//容斥原理,找到所有符合条件的正方形。 { while((px>=q[a[id]].x||py>=q[a[id]].y||qx<=p[a[id]].x||qy<=p[a[id]].y)&&id<num) { id++; } if(id>=num) { ans+=(qx-px)*(qy-py); return ; } if(px<p[a[id]].x) { inex(px,py,p[a[id]].x,qy,id+1); px=p[a[id]].x; } if(qx>q[a[id]].x) { inex(q[a[id]].x,py,qx,qy,id+1); qx=q[a[id]].x; } if(py<p[a[id]].y) { inex(px,py,qx,p[a[id]].y,id+1); } if(qy>q[a[id]].y) { inex(px,q[a[id]].y,qx,qy,id+1); } } int main() { int n,m,i,j,cas=0,ca; while(1) { RD(n); RD(m); if(n==0&&m==0) { break; } cas++; FOR(1,n,i) { RD(p[i].x); RD(p[i].y); RD(q[i].x); RD(q[i].y); } printf("Case %d:\n",cas); ca=0; while(m--) { ca++; RD(num); For(0,num,i) { RD(a[i]); } ans=0; For(0,num,i) { inex(p[a[i]].x,p[a[i]].y,q[a[i]].x,q[a[i]].y,i+1); } printf("Query %d: %d\n",ca,ans); } printf("\n"); } return 0; }