卡农题解

来源:互联网 发布:springmvc网站源码 编辑:程序博客网 时间:2024/04/27 14:16

题目描述

众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则。他将声音分成n个音阶,并将音乐分成若干个片段。音乐的每个片段都是由1到n个音阶构成的和声,即从n个音阶中挑选若干个音阶同时演奏出来。为了强调与卡农的不同,他规定任意两个片段所包含的音阶集合都不同。同时为了保持音乐的规律性,他还规定在一段音乐中每个音阶被奏响的次数为偶数。现在的问题是:小余想知道包含m个片段的音乐一共有多少种。两段音乐a和b同种当且仅当将a的片段重新排列后可以得到b。例如:假设a为{{1,2},{2,3}},b为{{3,2},{2,1}},那么a与b就是同种音乐。由于种数很多,你只需要输出答案模100000007(质数)的结果。

输入

从文件input.txt中读入数据,输入文件仅一行,具体是用空格隔开的两个正整数n和m,分别表示音阶的数量和音乐中的片段数。20%的数据满足n,m≤5,50%的数据满足n,m≤3000,100%的数据满足n,m≤1000000。

输出

输出文件output.txt仅包含一个非负整数,表示音乐的种数模100000007的结果。

样例输入

2 3

样例输出

1

提示

样例解释:音乐为{{1},{2},{1,2}}

想法

  • 初看题目是发现是一个子集选取的计数问题 很容易想到容斥
  • 观察到一个重要的性质 如果前i-1个集合已经确定那么 第i个集合就确定了 因为要满足每一元素出现的次数为偶数(该性质当时是由m=1 m=2的情况递推而来,同时也得到了当m=1,m=2时答案应该为0)
  • 减掉的部分有①第i个集合为空集的情况②第i个集合与之前的某项重复
  • 要注意C是不好求的而A却是好球的 完全可以先求A再除以m!

算法

  • 用f[i]表示选到第i个集合的方案数
  • f[i]=g[i-1] (g[i]为总数)-f[i-1] (第i项是空集)-f[i-2](i-1)(2^n-1-(i-2)) (第i项与之前的重复)
  • f[1]=f[2]=0 ans=f[m]/m!

代码

#include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <string>#include <cstring>#include <cmath>#define MAXN 1000005using namespace std;int n,m;typedef long long ll;const ll mod=100000007;ll f[MAXN],g[MAXN],o;inline ll pow(ll a,ll b,ll p){    ll r=1,base=a;    while(b)    {        if(b&1)r=r*base%p;        base=base*base%p;        b>>=1;    }    return r%mod;}/*inline ll pls(ll a,ll b){    ll res=1,cnt=1;    for (int i=1;i<=a;i++)res=res*i%mod;    for (int i=1;i<=(a-b);i++)cnt=cnt*i%mod;    return (res*pow(cnt,mod-2,mod))%mod;}*/int main(){    //freopen("canon.in","r",stdin);    //freopen("canon.out","w",stdout);    scanf("%d%d",&n,&m);    o=pow(2,n,mod)-1;    g[1]=o;    for (int i=2;i<=m;i++)        g[i]=g[i-1]*(o-i+1)%mod;    f[1]=f[2]=0;    for (int i=3;i<=m;i++)    {        f[i]=g[i-1]-f[i-1];        if(f[i]<0)f[i]+=mod;        f[i]-=f[i-2]*(i-1)%mod*(o-(i-2))%mod;        if(f[i]<0)f[i]+=mod;    }    o=1;    for (int i=1;i<=m;i++)o=o*i%mod;    cout<<f[m]*pow(o,mod-2,mod)%mod<<endl;    return 0;}
0 0
原创粉丝点击