[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、任意两个子集不相同。

3、任意子集非空。

至于顺序的问题,我们可以先统计出所有排列的方法,最后除以\(m!\)即可。

考虑设\(f_i\)表示\(i\)个子集的方案数。

我们先考虑满足第一个条件。

如果我们已经钦定了\(i-1\)个集合,那么最后一个集合也自然可以确定。

所以不考虑最后一个是否为空或者和之前的某个一样的情况的话,答案就是\(A_{2^n-1}^{i-1}\)

如果第\(i\)个子集空了,说明前面的偶数次这个条件就已经和法了,所以我们只需要减去\(f_{i-1}\)即可。

再考虑第i个子集和前面的子集重复的情况,同时这两个集合满足了偶数限制,去掉它之后同样满足。

所以方案数为\(f_{i-1}\),这个集合可能有\((2^n-1-(i-2))\)种选择,前面那个集合有\((i-1)\)种放法。

代码

#include<bits/stdc++.h>
#define N 1000009
using namespace std;
typedef long long ll;
const int mod=1e8+7;
ll p[N],jie[N],dp[N],n,m;
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline ll power(ll x,ll y){
    ll ans=1;
    while(y){
        if(y&1)ans=ans*x%mod;x=x*x%mod;y>>=1;
    }
    return ans;
}
int main(){
    n=rd();m=rd();
    ll ci=power(2,n);
    jie[0]=1;
    for(int i=1;i<=m;++i)jie[i]=jie[i-1]*i%mod;
    ll now=1,cii=ci;
    for(int i=1;i<=m;++i){
        p[i]=now;
        cii--;cii=(cii+mod)%mod;
        now=now*cii%mod;
    }
    dp[0]=1;dp[1]=0;
    for(int i=2;i<=m;++i){
        dp[i]=p[i]-dp[i-1];
        dp[i]-=dp[i-2]*(i-1)%mod*(ci-i+1)%mod;
        dp[i]=(dp[i]%mod+mod)%mod;
    }
    cout<<dp[m]*power(jie[m],mod-2)%mod;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/ZH-comld/p/10876910.html
今日推荐