一、鸽巢原理

内容回顾:

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;
}