BZOJ 2839 淺談容斥原理組合計數及歐拉定理優化二維冪指數

這裏寫圖片描述
世界真的很大
前幾天做了一道容斥原理的題,發現並不怎麼會這類東西,趕快找幾道題寫寫
看了幾篇題解不知所云
果然還是不簡單,按自己理解寫了過後發現過不了樣例
發現還是考慮的不仔細,認真研究一波之後才馬馬虎虎地搞懂了一知半解

看題先:

description:

一個有N個元素的集合有2^N個不同子集(包含空集),現在要在這2^N個集合中取出若干集合(至少一個),使得
它們的交集的元素個數爲K,求取法的方案數,答案模1000000007。(是質數喔~)

input:

一行兩個整數N,K

output:

一行爲答案。

不好意思之前的題解是口胡233
這道題並不是一個容斥原理那麼簡單,但是網上的題解都沒有提到這一點。。。做了一點補充希望耐心看完

這道題他說“交集元素個數剛好爲K”的方案數
那麼就很容易想到容斥原理,即:
大於等於K的 - 大於等於K+1的 + 大於等於K+2的 and so on
那麼現在的問題就在於求出來大於等於某個值的方案數
比如現在來找一下交集大於等於 i 的方案數
首先枚舉交集i,即從n個裏面選i個作爲交集元素的方案數,這一步自然是 C(n,i)。
然後考慮整個集合還剩n-i個元素,剩餘的子集個數就是2^(n-i)
現在我們相當於是給每一個剩下的子集添加這i個元素,這樣他們的交集就至少爲i了
然後剩下的每一個子集有選與不選,這樣方案數就是2^(2^(n-i))
但是所有集合不能同時不選
也許會有疑問全都不選就選這i個不也是一種方案嗎?
其實不然
全都不選等價於選了空集,而空集也是屬於那2^(n-i)種子集的
全都不選即是說空集也不選,這當然是不合法的,所以要減一
即: 2^(2^(n-i))-1

然後這樣算一下,寫出來發現,過不了樣例。。
樣例3 2
那我們枚舉的其實就是大於等於2的減去大於等於3的
大於等於二的子集選擇,有:
{{1,2}},{{1,2,3}},{{1,2},{1,2,3}}
{{1,3}},{{1,3,2}},{{1,3},{1,3,2}}
{{3,2}},{{3,2,1}},{{3,2},{3,2,1}}
九種
大於等於3的子集選擇,有:
{{1,2,3}}一種,一減。
咦?不是8嗎?樣例答案怎麼是6啊?
仔細一看,在前九種中,{123},{132},{321}作爲同樣的集合出現了3次,因爲在枚舉{12},{13},{32}時,他們都作爲必須包含的子集的擴充出現了,所以統計的時候統計了三次
但是減去的大於等於3的子集方案數卻只減了一種,這是由於枚舉的{123}本身只被當做一種方案了
即是說,在枚舉之後的值i的時候,我們還應該考慮在之前的計算當中,被算重複了多少次,不能單單減了了事
那麼到底減多少呢?
就是說,當前這個值加加減減多少次,取決於之前這個值被算重了幾次
一個集合如{123}被算重複了多少次,即是說在枚舉多少個K值的時候,會枚舉到{123}
比如說在枚舉{12},{23},{13}的時候,都會枚舉到{123}。
那麼對於一個{123},其有幾個K的組合,不就是會算重幾次嗎?
於是乎在乘上一個C(i,K)就行了

看上去沒什麼問題了,這大概就是網上題解的思路吧,以至於我第一次做的時候就被這個繞暈了
容斥原理解決的是集合的並的問題。講道理就這道題而言憑什麼容斥的時候要帶一個係數C(i,k)?
帶上C(i,k)之後還是我們要的交集大於等於i的字節選擇方案數嗎?如果不是那每一步容斥又代表的是什麼意思?
儘管算出來的確是那麼一回事但是就按一開始的寫法,根據其代表的意思而言爲什麼不對?
即:大於等於K的 - 大於等於K+1的 + 大於等於K+2的 and so on求出來的值憑什麼不是答案?憑什麼非要帶一個係數?

其實這個式子根本就不是容斥原理!
容斥原理解決的只有特定集合的並和交,“大於等於K的” 還有“ 大於等於K+1的”有可能根本就沒有交集憑什麼能相減?
其實是這樣的,我們枚舉每一個大小爲K的子集X,再枚舉其每一個包含它的超集,還有其對應的選擇方案數,即
sigma (-1)^(i-K) * 2^(2^(n-i))-1 ;
這樣纔是,“選擇子集和,交集爲特定集合X的方案數”,而求出他的過程,就是容斥原理
這樣的X有多少個呢?,有C(n,K)個,所以答案其實是進行了C(n,K)次容斥原理之後累加的答案:
C(n,K) * sigma (-1)^(i-K) * 2^(2^(n-i))-1 ;
但是可以化簡。
考慮每一個超集,再每一個枚舉到他的容斥原理裏面,所帶的係數都是一樣的,所以我們可以考慮枚舉超集和其所帶的係數的加加減減,那麼這樣一個超集會出現在多少個容斥裏面呢?就是說在多少個容斥裏面枚舉到他了呢?就是說他包含了多少個K呢?
C(i,K)個,這纔是那個組合數的由來
這樣的同大小的超集有多少個呢?
C(n,i)個
所以式子化簡後纔是:
sigma C(n,i) * C(i,K) * 2^(2^(n-i))-1 ;

即這並不是一個單獨的容斥原理,而是C(n,K)次容斥原理的合式,提取公因式後的樣子

注意在計算2^(2^(n-i))次方的時候,由於歐拉定理,
a^b mod m=a^(b%phi(m)) mod m
所以說在裏面的快速冪的時候應該mod m的歐拉函數值而不是m
特別注意

完整代碼:

#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long dnt;

const dnt mod=1e9+7;

dnt n,K;
dnt ans=0,saber[2000020],inv[2000020];

void init()
{
    saber[0]=inv[0]=inv[1]=1;
    for(int i=1;i<=2000000;i++) saber[i]=saber[i-1]*i%mod;
    for(int i=2;i<=2000000;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=2000000;i++) inv[i]=inv[i-1]*inv[i]%mod;
}

dnt quickmub(dnt a,dnt b,dnt m)
{
    dnt rt=1;
    while(b)
    {
        if(b&1) rt=(rt*a)%m;
        a=(a*a)%m,b>>=1;
    }
    return rt;
}

dnt Misaka(dnt a,dnt b)
{
    if(a<b) return 0;
    return saber[a]*inv[b]%mod*inv[a-b]%mod;
}

dnt fix(dnt a)
{
    return (a%mod+mod)%mod;
}

int main()
{
    init();
    scanf("%I64d%I64d",&n,&K);
    for(int i=n;i>=K;i--)
    {
        dnt flag=(i-K&1) ? -1 : 1;
        dnt tmp=fix(quickmub(2,quickmub(2,n-i,mod-1),mod)-1);
        ans=fix(ans+flag*Misaka(n,i)%mod*Misaka(i,K)%mod*tmp%mod)%mod;
    }
    cout << ans << endl;
    return 0;
}
/*
EL PSY CONGROO
*/

嗯,就是這樣

發佈了163 篇原創文章 · 獲贊 21 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章