鸽巢原理和容斥原理

鸽巢原理又名抽屉原理

一种简单的表述法为:

  • 若有n个笼子和n+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少2只鸽子

另一种为:

  • 若有n个笼子和kn+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少k+1只鸽子

例子:

  • 盒子里有10只黑袜子、12只蓝袜子,你需要拿一对同色的出来。假设你总共只能拿一次,只要3只就可以拿到相同颜色的袜子,因为颜色只有两种(鸽巢只有两个),而三只袜子(三只鸽子),从而得到“拿3只袜子出来,就能保证有一双同色”的结论。
  • 有n个人(至少2人)互相握手(随意找人握),必有两人握手次数相同。

一种表达是这样的:如果要把n个物件分配到m个容器中,必有至少一个容器容纳至少⌈n / m⌉个物件。(⌈x⌉大于等于x的最小的整数)

 

容斥原理又称排容原理

(1)两个集合容斥关系

(2)三个集合容斥关系

(3)n个集合的容斥关系

即1个集合的并-2个集合的并+3个集合的并-4个集合的并+5个集合的并......

 

鸽巢:

pku2365 Find a multiple

 题目意思是给n个数,选择其中一些数,使他们的和是n的k倍,k是自然数。只要输出其中正确的解就行

分析:

这n个数有n个前缀和,s[0],s[1],s[2]...s[n],并且对n取余

如果有s[i]==0,则输出0-i之间的数

如果不存在s[i]==0, 则s[i]一定在[1,n-1]之间 ,

根据鸽巢原理,n个物体放入n-1个盒子,至少有一个盒子有两个及以上的物体,所以题目一定有解

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 10010
int n;
int arr[maxn],s[maxn],h[maxn];
int main()
{
    while(~scanf("%d",&n))
    {
        memset(h,-1,sizeof(h));
        int x;
        for(int i=0;i<n;i++){
            scanf("%d",&arr[i]);
            s[i]=(s[i-1]+arr[i])%n;
        }
        int a,b;
        for(int i=0;i<n;i++)
        {
            if(s[i]==0)
            {
                a=0;
                b=i;
                break;
            }
            else{
                if(h[s[i]]!=-1)
                {
                    a=h[s[i]]+1;
                    b=i;
                    break;
                }
                else h[s[i]]=i;
            }
        }
        printf("%d\n",b-a+1);
        for(int i=a;i<=b;i++)printf("%d\n",arr[i]);
    }
    return 0;
}
View Code

pku3370 Halloween treats

 和pku2365相同,c个孩子在n个neighbor中要选择糖果,使得糖果总和是c的倍数

注意鸽巢原理的使用范围,有n-1个盒子,至少n个物体,如果neighbor给的糖数为0,则盒子数量要减少,当然没有这种数据了

tip:数组下标尽量不要用-1,或者每次让s[-1]=0,进行初始化。这个地址由于没有跟着数组空间一起初始化,
所以其中的数据是不一定的,
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 100010
#define ll long long
int c,n;
int h[maxn],a[maxn],x;
int main()
{
    while(~scanf("%d%d",&c,&n))
    {
        if(c==0&&n==0)break;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        memset(h,-1,sizeof(h));
        int s=0;
        h[0]=0;
        for(int i=1;i<=n;i++)
        {
            s=(s+a[i])%c;
          //  printf("* %d %d\n",i,s);
            if(h[s]!=-1)
            {
                for(int j=h[s]+1;j<i;j++)
                {
                    printf("%d ",j);
                }
                printf("%d\n",i);
                break;
            }
            else h[s]=i;
        }
    }
    return 0;
}
View Code

容斥:

hdu1695 GCD 

x=[a,b] , y=[c,d]  求gcd(x,y)=k 的组合数 ,a=1,c=1 ,(x,y),(y,x)是同一种组合

用容斥原理+二进制枚举。

先把b/=k ,d/=k

问题变成x=[1,b]  y=[1,d] ,其中(x,y)互质的对数

枚举x,预处理x的质因子,假设w是x的质因子,1-d中w的倍数有d/w个

假设f(n)表示1-d中n个x的质因子乘积的倍数

容斥原理:不与x互质的个数=f(1)-f(2)+f(3)-f(4)...

注意:答案很大,用long long存

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define maxn 100010
#define ll long long
vector<int>v[maxn];
bool is[maxn];
void prime()
{
    for(int i=0;i<maxn;i++)v[i].clear();
    memset(is,false,sizeof(is));
    is[0]=is[1]=true;
    for(int i=2;i<maxn;i++)
    {
        for(int j=i;j<maxn;j+=i)
        {
            if(!is[i])
            {
                v[j].push_back(i);
                if(j>i)is[j]=true;
            }
        }
    }
}
int work(int u,int s,int d)// d是范围1-d s是状态压缩 u是素因子表
{
    int cnt=0;
    int mul=1;
    for(int i=0;i<v[u].size();i++)
    {
        if((1<<i)&s){
            mul*=v[u][i];
            cnt++;
        }
    }
    int all=d/mul-(u-1)/mul;//u-d 内s选择状态因子的倍数的个数(d>u)
    if(cnt%2==0)all=-all;
    return all;
}
int main()
{
    prime();
    int T;
    scanf("%d",&T);
    for(int ca=1;ca<=T;ca++)
    {
        int a,b,c,d,k;
        ll ans;
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        if(k==0)ans=0;
        else{
            ans=0;
            b/=k;d/=k;
            if(b>d)b^=d^=b^=d;
            for(int i=1;i<=b;i++)
            {
                ans+=d-i+1;//(x,y) 和(y,x)算一种 选择范围i-d
                int p=(1<<v[i].size());//p是素因子是否选取的所有状态
                for(int j=1;j<p;j++)
                {
                    ans-=work(i,j,d);
                }
            }
        }
        printf("Case %d: %lld\n",ca,ans);
    }
    return 0;
}
/*
2
1 1 1 5 1
1 5 1 1 1
*/
View Code

hdu2461 Rectangles

给你n(n很小)个长方形,求这中间任意长方形的面积并。容斥

tip:结构体数组会初始化为0,结构体变量不会初始化为0

因为这个tle,还以为是算法错了

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
struct rec
{
    int x1,y1,x2,y2;
}a[30];
vector<int>v;
int s[(1<<20)+10];
rec intersec(rec a, rec b)
{
    rec c;
    if(a.x2<=b.x1||a.y2<=b.y1||a.x1>=b.x2||a.y1>=b.y2){
        c.x1=c.x2=c.y1=c.y2=0;//注意没有交集要赋值0,否则tle,In_exclusion中停不下来!
        return c;
    }
    c.x1=max(a.x1,b.x1);
    c.y1=max(a.y1,b.y1);
    c.x2=min(a.x2,b.x2);
    c.y2=min(a.y2,b.y2);
    return c;
}
int Area(rec r)
{
    if(r.x1>=r.x2||r.y1>=r.y2)
        return 0;
    return (r.y2-r.y1)*(r.x2-r.x1);
}
int In_exclusion(int k,rec r)
{
    if(k>=v.size()||Area(r)==0)
    {
        return 0;
    }
    int ret=0;
    for(int i=k;i<v.size();i++)
    {
        rec tmp=intersec(a[v[i]],r);
        ret+=Area(tmp)-In_exclusion(i+1,tmp);
    }
    return ret;
}
int main()
{
    rec tot;
    tot.x1=tot.y1=0;
    tot.x2=tot.y2=1000;
    int ca=1;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)break;
        printf("Case %d:\n",ca++);
        for(int i=1;i<=n;i++)scanf("%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
        memset(s,0,sizeof(s));//do not forget else get tle
        for(int i=1;i<=m;i++)
        {
            v.clear();
            int R;
            int id;
            scanf("%d",&R);
            int add=0;
            while(R--)
            {
                scanf("%d",&id);
                v.push_back(id);
                add=add|(1<<(id-1));
            }
            if(s[add]==0)s[add]=In_exclusion(0,tot);
            printf("Query %d: %d\n",i,s[add]);
        }
        puts("");
    }
    return 0;
}
/*
3 2
0 0 2 3
1 1 4 5
0 2 3 4
2 1 2
3 1 2 3
*/
View Code

 

转载于:https://www.cnblogs.com/kylehz/p/4414333.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值