鸽巢原理和容斥原理小结

一、鸽巢原理

内容回顾:

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;

}


 


 

你可能感兴趣的:(原理)