卡农题解

本文介绍了小余根据卡农音乐灵感创新的音乐谱写规则,其中每个音乐片段包含不同的音阶,每个音阶演奏次数为偶数。问题在于计算特定数量片段的音乐组合数,考虑了子集计数和避免重复的约束。给出了输入输出格式,以及解决思路和算法描述,最终计算答案模100000007的结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则。他将声音分成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 1000005
using 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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值