1. 程式人生 > >P3214 [HNOI2011]卡農

P3214 [HNOI2011]卡農

題目

P3214 [HNOI2011]卡農

在被一題容斥\(dp\)完虐之後,打算做一做集合容斥這類的題了

第一次深感HNOI的毒瘤(題做得太少了!!)

做法

\([1,n]\)組成的集合中選\(m\)個不同集合且每個元素出現偶數的組合方案

無序(打亂順序仍記為一種)通常我們對於有序的做法更簡單,怎麼轉換呢

C組合數的公式是怎麼得來的?別說你是背來的\(emmm\)(那也沒有做這題的必要了)

有序\(m!\)就得到了無序的

我們考慮\(dp\),陣列\(dp_i\)表示選i個不同集合的排列方案

異或和為\(0\),則,確定前\(i-1\)個集合則第\(i\)個集合自然也出來了,方案數為\(A_{2^n-1}^{i-1}\)

如果前面\(i-1\)個集合異或和已為\(0\),那第\(i\)個集合為空集,不符題意,這部分的方案數就是\(dp_{i-1}\)

保證所選集合不重複,若\(i\)與前\(i-1\)任意重複,去掉這個重複的集合,為\(dp_{i-2}\),可能的位置有\((i-1)\)個,重複集合個數有\((2^n-1-(i-2))\)

\(dp_i=A_{2^n-1}^{i-1}-dp_{i-1}-dp_{i-2}*(i-1)*(2^n-i+1)\)

最後再乘下逆元就好了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL p=100000007;
const int maxn=1e6+9;
inline LL Pow(LL base,LL b){
    LL ret(1);
    while(b){
        if(b&1)
            ret=ret*base%p;
        base=base*base%p,
        b>>=1;
    }
    return ret;
}
LL n,m,a,Up,A,ans;
LL dp[maxn];
int main(){
    scanf("%lld%lld",&n,&m);
    dp[1]=dp[2]=0,
    Up=(Pow(2ll,n)-1ll+p)%p,
    A=Up;
    for(LL i=3;i<=m;++i)
        A=A*(Up-i+2)%p,
        dp[i]=((A-dp[i-1]+p)%p-dp[i-2]*(i-1)%p*((Up-(i-2)+p)%p)%p +p)%p;
    a=1;
    for(LL i=2;i<=m;++i)
        a=a*i%p;
    ans=dp[m]*Pow(a,p-2)%p;
    printf("%lld\n",ans);
    return 0;
}/*
100 1000
*/