容斥定理+鴿巢原理
容斥定理
要計算幾個集合並集的大小,我們要先將所有單個集合的大小計算出來,然後減去所有兩個集合相交的部分,再加回所有三個集合相交的部分,再減去所有四個集合相交的部分,依此類推,一直計算到所有集合相交的部分。
容斥原理:在計數時,必須注意無一重複,無一遺漏。爲了使重疊部分不被重複計算,人們研究出一種新的計數方法,這種方法的基本思想是:先不考慮重疊的情況,把包含於某內容中的所有對象的數目先計算出來,然後再把計數時重複計算的數目排斥出去,使得計算的結果既無遺漏又無重複,這種計數的方法稱爲容斥原理。
公式表述
公式的解釋:
目的是求解m個集合的並集,首先將m個集合相加,減去集合間兩兩相交的部分,加上三三相交的部分,再減去四四相交的部分……一直到m個集合相交的部分,當m爲偶數時,最後一項的符號爲負數,否則爲正數。
舉例
- 當存在兩個集合A與B,容斥關係公式爲:A∪B =|A∪B| = |A|+|B| - |A∩B |
- 當存在三個集合A、B、C,容斥關係公式爲:|A∪B∪C| = |A|+|B|+|C| - |A∩B| - |B∩C| - |C∩A| + |A∩B∩C|
簡單的說就是選中的是奇數個集合就相加,選中的是偶數個集合就相減,
在這裏我們介紹一種方法那就是二進制枚舉法;
我們先來思考一下這個問題:如果要對n個物體進行選擇,那麼有多少種情況?
這個我們應該怎麼做呢?每個問題只有兩種情況,那就是選與不選,這就相當於十幾個集合求並集,如果選的是奇數個物品那麼我們應該去加的,如果選的是偶數的物品,那麼我們應該是相減的,但是我們應該怎麼選呢?我們要實現的目的就是每次選的時候我們呢要知道哦我們選了多少個。而且我們還要知道我們選看誰,那麼該怎麼辦呢?這就用到二進制枚舉法了,既然這個方法的名字和二進制有關那麼我們肯定也要用到二進制了是吧;
假如我們有三件物品,我們會有8種選擇方案,我們先把他們一一的列舉出來2,看看有什麼規矩沒;
他們所對應的十進制數
0 0 0 0
0 0 1 1
0 1 0 2
0 1 1 3
1 0 0 4
1 0 1 5
1 1 0 6
1 1 1 7
是不是一共這8種情況,看一下這些像不像是二進制呢?那麼我們先把他們看一下他們對應的十進制數,把二進制寫出來之後,大家有沒有發現什麼規矩呢?好像這些數字就是從0到2的n次方-1是吧。那麼我們我們把這些數字遍歷一下是不是就把所有可以選的情況全部遍歷了呢?然後我們再用一個位運算,例如6對應的是1 0 1,我們可以分別取 1,0,1那麼我們就知道了我們一共選擇了兩個物品,選擇的物品分別是第一個物品和第三個物品。
代碼實現:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;//一共n個物品;
scanf("%d",&n);
for(int i=0;i<(1<<n);i++)//從0到2的n次方-1;
{
for(int j=0;j<n;j++) //判斷第j個物品有沒有被選中;
printf("%d ",1&(i>>j));
printf("\n");
}
return 0;
}
/*
運行結果
3
0 0 0
1 0 0
0 1 0
1 1 0
0 0 1
1 0 1
0 1 1
1 1 1
*/
例題:C;
題意:
這個題的大致意思就是說給你一個區間a到b,再給你一個數字n,找一下在所給的區間中和n互質的個數,
這個題我們應該怎麼做呢?首先我們應該先把n用質因子分解法把n分解成幾個質數,然後再找出和n的質因子不互質,也就是是n的質因子的倍數的個數,至於區間不是從1開始的這個問題也很好解決,我們只需要看成兩個區間最後再一減就行了;
第一步:求出n的質因子:2,3,5;
第二步:(1,m)中是n的因子的倍數當然就不互質了(2,4,6,8,10)->n/2 6個,(3,6,9,12)->n/3 4個,(5,10)->n/5 2個。
如果是粗心的同學就把它們全部加起來就是:6+4+2=12個了,那你就大錯特錯了,裏面明顯出現了重複的,我們現在要處理的就是如何去掉那些重複的了!
第三步:這裏就需要用到容斥原理了,公式就是:n/2+n/3+n/5-n/(2*3)-n/(2*5)-n/(3*5)+n/(2*3*5).
第四步:我們該如何實現呢?就用我剛纔講的二進制枚舉。
代碼:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define met(Q,QQ) memset(Q,QQ,sizeof(Q))
int main()
{
ll t,A,B,n,a[103];
scanf("%lld",&t);
ll aaa=0;
while(t--)
{
aaa++;
met(a,0);
scanf("%lld %lld %lld",&A,&B,&n);
ll cnt=0;
for(int i=2;i*i<=n;i++) //質因子分解
{
if(n%i==0)
{
cnt++;
a[cnt]=i;
}
while(n%i==0)
{
n/=i;
}
}
if(n!=1)
{
cnt++;
a[cnt]=n;
}
ll SUM=0;
for(int i=1;i<(1<<cnt);i++)//二進制枚舉
{
ll sum=1,cnt1=0;
for(int j=0;j<cnt;j++)
{
if(1&(i>>j)) //判斷有沒有選中這個集合,
{
sum*=a[j+1]; //選中的數字的乘積
cnt1++;; //選中的個數
}
}
if(cnt1&1)
{
SUM+=B/sum;
SUM-=(A-1)/sum;
}
else
{
SUM-=B/sum;
SUM+=(A-1)/sum;
}
}
printf("Case #%lld: %lld\n",aaa,B-A+1-SUM);
}
return 0;
}
鴿巢原理:
基本描述
桌子上有是個蘋果,把這十個蘋果放到九個抽屜裏,無論怎麼放,我們會發現至少會有一個抽屜裏面至少放兩個蘋果。這一現象就是所說的“抽屜原理”。
更一般的表述:如果每一個抽屜代表一個集合,每一個蘋果就可以代表一個元素。加入有n+1個元素放到n個集合中去,其中必定有一個集合裏至少有兩個元素。
第一抽屜原理
原理1
把多餘n+1個物體放到n個抽屜裏,則至少有一個抽屜裏的東西不少於兩件。
原理2
把多餘mn+1(n不爲0)個物體放到n個抽屜裏面,則至少有一個抽屜裏面不少於(m+1)的物體。
第二抽屜原理
把(mn -1 )個物體放入n個抽屜中,其中必須有一個抽屜不多餘(m-1)個物體。
如將3*5-1 = 14個物體放入5個抽屜中,則必定有一個抽屜中的物體數目少於3-1=2.
舉例
屬相問題
屬相有12個,那麼任意37個人中,至少有幾個人屬相相同?
上取整(37 / 12) = 4
招聘問題
有300人到招聘會求職,其中軟件設計有100人,市場營銷有80人,財務管理有70人,人力資源管理有50人。那麼至少有多少人找到工作才能保證一定有70人找的工作專業相同?
考慮最差情況,即軟件設計,市場營銷,財務管理均招了69人,人力資源管理招了50人,此時再多招1人,就有70人找的工作專業相同了。
故答案爲 69*3 + 50 + 1 = 258
襯衫問題
一個抽屜裏有20件襯衫,其中4件是藍的,7件是灰的,9件是紅的,則應從中隨意取出多少件才能保證有5件是同顏色的?
考慮最差情況,即已經取出了4件藍色,4件灰色,4件紅色,再多取出1件就滿足條件。
故答案爲 4 + 4 + 4 + 1 = 13
例題 D
題目大意就是先給出一個數N,接着再給出N個數,要你從這N個數中任意選擇1個或多個數,使得其和是N的倍數
如果找不到這樣的答案 則輸出0
答案可能有多個,但智勇任意輸出一個解就行。
輸出的第一行是選擇元素的個數M,接着M行分別是選擇的元素的值
剛開始的時候並不同爲什麼這一題回事抽屜原理,分析後才明白,昨晚後更有體會
實際上此題一定有解,不存在輸出0的結果
證明如下
我們可以依次求出a[0],a[0]+a[1],a[0]+a[1]+a[2],......,a[0]+a[1]+a[2]...+a[n];
假設分別是sum[0],sum[1],sum[2],......,sum[n]
如果在某一項存在是N的倍數,則很好解,即可直接從第一項開始直接輸出答案
但如果不存在,則sum[i]%N的值必定在[1,N-1]之間,又由於有n項sum,有抽屜原理:
把多於n個的物體放到n個抽屜裏,則至少有一個抽屜裏有2個或2個以上的物體。
則必定有一對i,j,使得sum[i]=sum[j],其中i!=j,不妨設j>i
則(sum[j]-sum[i])%N=0,故sum[j]-sum[i]是N的倍數
則只要輸出從i+1~j的所有的a的值就是答案
代碼:
#include<stdio.h>
#include<string.h>
using namespace std;
#define met(Q,QQ) memset(Q,QQ,sizeof(QQ))
const int maxn=1e4+5;
int sum[maxn],a[maxn];//取模後的前綴和
int b[maxn];//標記作用,判斷這個前綴和前面有沒有出現過,
int main()
{
int n;
met(sum,0);
met(b,0);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=(sum[i-1]+a[i])%n;
}
for(int i=1;i<=n;i++)
{
if(sum[i]==0)
{
printf("%d\n",i);
for(int j=1;j<=i;j++)
{
if(j!=1) printf(" %d",a[j]);
else printf("%d",a[j]);
}
break;
}
if(b[sum[i]]==0)
{
b[sum[i]]=i;
}
else
{
printf("%d\n",i-b[sum[i]]);
for(int j=b[sum[i]]+1;j<=i;j++)
{
if(j!=b[sum[i]]+1) printf(" %d",a[j]);
else printf("%d",a[j]);
}
break;
}
}
return 0;
}