1. 程式人生 > >[bzoj2339][HNOI2011]卡農——動態規劃+容斥原理

[bzoj2339][HNOI2011]卡農——動態規劃+容斥原理

題目大意:

眾所周知卡農是一種復調音樂的寫作技法,小余在聽卡農音樂時靈感大發,發明了一種新的音樂譜寫規則。他將聲音分成 n 個音階,並將音樂分成若干個片段。音樂的每個片段都是由 1 到 n 個音階構成的和聲,即從 n 個音階中挑選若干個音階同時演奏出來。為了強調與卡農的不同,他規定任意兩個片段所包含的音階集合都不同。同時為了保持音樂的規律性,他還規定在一段音樂中每個音階被奏響的次數為偶數。現在的問題是:小余想知道包含 m 個片段的音樂一共有多少種。兩段音樂 a 和 b 同種當且僅當將 a 的片段重新排列後可以得到 b。例如:假設 a
為{{1,2},{2,3}},b 為{{3,2},{2,1}},那麼 a 與 b 就是同種音樂。由於種數很多,你只需要
輸出答案模 100000007(質數)的結果。

思路:

首先我們可以得到一共有\(2^n-1\)種合法的集合。
由於每一個元素都必須要出現偶數次,所以我們如果選了\(m-1\)個片段,那麼最後一個片段就可以直接根據奇偶性確定。
\(2^n-1\)個片段中選擇\(m-1\)個的方案為\({2^n-1\choose m-1}\),這時候組合數顯然難以計算,於是我們將片段之間從無序變得有序,之後再將答案除以一個\(m!\)即可。
考慮記\(f_m\)為m個片段合法的方案數,如果令\(f_m=A_{2^n-1}^{m-1}\),那麼我們會發現最後一個片段有可能為空集,同時也有可能是之前出現過的片段,於是我們要將這些不合法的情況給刪除。
如果是空集,那麼可以保證前\(m-1\)

個片段的方案數合法,那麼直接減去\(f_{m-1}\)
如果是前面出現過的片段,考慮列舉和它相同的片段的位置是哪一個(總共\(m-1\)個位置),然後剩下來的\(m-2\)個片段的情況也一定是合法的,共有\(f_{m-2}\)種,確定了這\(m-2\)中之後,有\(2^n-1-(m-2)\)個片段可以供這個位置挑選,所以要減去的情況是\((m-1)\times f_{m-2}\times (2^n-1-(m-2))\)
然後就可以直接遞推了。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define MREP(i,x) for(int i=beg[x],v;v=to[i],i;i=las[i])
#define debug(x) cout<<#x<<"="<<x<<endl
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj2339.in","r",stdin);
    freopen("bzoj2339.out","w",stdout);
}

template<typename T>void read(T &_){
    T __=0,mul=1; char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')mul=-1;
        ch=getchar();
    }
    while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    _=__*mul;
}

const int maxn=1e6+10;
const ll mod=1e8+7;
int n,m;
ll p2,fac[maxn],f[maxn];

ll qpow(ll x,ll y){
    ll ret=1; x%=mod;
    while(y){
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ret;
}

int main(){
    //File();
    read(n),read(m);

    p2=(qpow(2,n)-1)%mod;

    fac[0]=1;
    REP(i,1,m)fac[i]=fac[i-1]*(p2-i+1)%mod;

    f[0]=1,f[1]=0;
    REP(i,2,m){
        f[i]=fac[i-1];
        f[i]=(f[i]-f[i-1])%mod;
        f[i]=(f[i]-(i-1)*f[i-2]%mod*(p2-i+2)%mod)%mod;
    }

    ll tmp=1;
    REP(i,1,m)tmp=tmp*i%mod;

    f[m]=f[m]*qpow(tmp,mod-2)%mod;

    printf("%lld\n",(f[m]+mod)%mod);

    return 0;
}