鸽巢原理和容斥原理

鸽巢原理又名抽屉原理

一种简单的表述法为:

  • 若有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;
}

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

容斥:

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
*/

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
*/

时间: 2024-07-30 05:21:34

鸽巢原理和容斥原理的相关文章

鸽巢原理简单应用

http://poj.org/problem?id=2356 从n个数里面取出一些数,这些数的和是n的倍数.并输出这些数. 先预处理出前n个数的和用sum[i]表示前i个数的和.若某个sum[i]是n的倍数,直接输出前i个数即可. 否则说明n个数中对n取余的结果有n-1种,即余数为(1~n-1),根据鸽巢原理知必定至少存在两个sum[i]与sum[j]对n取余的结果相等.那么i+1 ~ j之间的数之和一定是n的倍数. #include <stdio.h> #include <iostre

poj 2356 Find a multiple 鸽巢原理的简单应用

题目要求任选几个自然数,使得他们的和是n的倍数. 由鸽巢原理如果我们只选连续的数,一定能得到解. 首先预处理前缀和模n下的sum,如果发现sum[i]==sum[j] 那么(sum[j]-sum[i])%n一定为0,直接输出i+1~j就够了. 为什么一定会有解,因为sum从1~n有n个数,而模n下的数只有0~n-1,把n个数放入0~n-1个数里,怎么也会有重复,所以这种构造方法一定没问题. 其实可以O(n)实现,嫌麻烦,就二重循环无脑了. #include <iostream> #includ

鸽巢原理-poj3370

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> int main(int argc, char *argv[]) {         int c = -1, n = -1;         while (true) {         scanf("%d%d",

骚操作之鸽巢原理

桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面至少放两个苹果.这一现象就是我们所说的"抽屉原理". 抽屉原理的一般含义为:"如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素." 抽屉原理有时也被称为鸽巢原理.它是组合数学中一个重要的原理. 在acm中也是会遇到的,比如两个人对打的得分问题 110个人参加一个国际象棋单循环比赛,每两人都进行一局比赛,

HDU 1205.吃糖果【鸽巢原理】【8月1】

吃糖果 Problem Description HOHO,终于从Speakless手上赢走了所有的糖果,是Gardon吃糖果时有个特殊的癖好,就是不喜欢将一样的糖果放在一起吃,喜欢先吃一种,下一次吃另一种,这样:可是Gardon不知道是否存在一种吃糖果的顺序使得他能把所有糖果都吃完?请你写个程序帮忙计算一下. Input 第一行有一个整数T,接下来T组数据,每组数据占2行,第一行是一个整数N(0<N<=1000000),第二行是N个数,表示N种糖果的数目Mi(0<Mi<=10000

POJ 2356 Find a multiple 鸽巢原理

题目来源:POJ 2356 Find a multiple 题意:n个数 选出任意个数 使得这些数的和是n的倍数 思路:肯定有解 并且解是连续的一段数 证明: 假设有m个数 a1,a2,a3...am    s1 s2 s3...sm为前缀和 s1 = a1 s2 = a1+a2 s3 = a1+a2+a3... sm = a1+a2+a3+...+am 1.如果某个前缀和si%m == 0 那么得到解 2.设x1=s1%m x2 = s2%m x3 = s3%m xm = sm%m 因为1不成

HDU3183(RMQ+鸽巢原理)

题目的意思是对于一个n位数,删除m个位后,得到的最小数是什么,比如12345 2,删除两个位,得到最小的就是123.实际上这题目解法很多,好像有贪心,线段树,RMQ等,因为我最近在学习RMQ,所以就用RMQ了. 这题目用了一个鸽巢原理,得到的m-n位数的第一位,必然出现在1~m-n+1,这个由鸽巢原理就十分明显了(如果1~n-(m-n)+1都没有的话,剩下的m-n-1个位是不可能凑出m-n个位的数的!)这样我们就可以从[1,n-(m-n)+1]中作RMQ取得最小值下标i,之后对于i+1后,m-n

鸽巢原理

鸽巢原理: n+1个鸽子放入n个窝中,至少有一个窝含有两只鸽子  Find a multiple Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 5590   Accepted: 2434   Special Judge Description The input contains N natural (i.e. positive integer) numbers ( N <= 10000 ). Each of that

POJ 2356. Find a multiple 抽屉/鸽巢原理

Find a multiple Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 7192   Accepted: 3138   Special Judge Description The input contains N natural (i.e. positive integer) numbers ( N <= 10000 ). Each of that numbers is not greater than 15000