一、鸽巢原理
内容回顾:
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;
}