zoukankan      html  css  js  c++  java
  • 鸽巢原理和容斥原理小结

    一、鸽巢原理

    内容回顾:

    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
    ",i);
                FOR(1,i,j)
                {
                    OT(a[j]);
                    printf("
    ");
                }
                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
    ",q-p);
            FOR(p+1,q,i)
            {
                OT(a[i]);
                printf("
    ");
            }
        }
        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("
    ");
                    break;
                }
                else
                {
                    if(g[sum[i]])
                    {
                        OT(g[sum[i]]+1);
                        FOR(g[sum[i]]+2,i,j)
                        {
                            printf(" %d",j);
                        }
                        printf("
    ");
                        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
    ",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:
    ",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
    ",ca,ans);
            }
            printf("
    ");
        }
        return 0;
    }



  • 相关阅读:
    .NET ------- 根据关键字查询后,点击详细页面对关键字标红
    .NET ------ 禁止文本输入的三种方式
    .NET ---- 借助repeater在行中进行下拉框编辑 (前端赋值给下拉框)
    Android ------ Android Studio 生成 apk 文件
    CentOS 8 Stream 简单的网络配置
    最受欢迎的 10 本编程书籍(文末附地址)
    优秀程序员必须掌握的 8 种通用数据结构
    一次阿里 P7 的面经,分享给大家
    如何在 Windows 上运行 Linux? 这里有一份攻略!
    为啥程序员下班后从不关电脑?
  • 原文地址:https://www.cnblogs.com/james1207/p/3289772.html
Copyright © 2011-2022 走看看