【洛谷P3214】卡农 题目 思路 代码
题目链接:https://www.luogu.com.cn/problem/P3214
众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则。
他将声音分成 (n) 个音阶,并将音乐分成若干个片段。音乐的每个片段都是由 1 到 (n) 个音阶构成的和声,即从 (n) 个音阶中挑选若干个音阶同时演奏出来。
为了强调与卡农的不同,他规定任意两个片段所包含的音阶集合都不同。同时为了保持音乐的规律性,他还规定在一段音乐中每个音阶被奏响的次数为偶数。
现在的问题是:小余想知道包含 (m) 个片段的音乐一共有多少种。
两段音乐 (a) 和 (b) 同种当且仅当将 (a) 的片段重新排列后可以得到 (b)。例如:假设 (a) 为 ({{1,2},{2,3}}),(b) 为 ({{2,3},{1,2}}),那么 (a) 与 (b) 就是同种音乐。
答案对 (10^8+7) 取模。
(n,mleq 10^6)。
思路
理一下题目内容发现我们需要求出满足以下几个条件的序列数量:
- 序列中每一个元素所表示的集合元素均在 ([1,n]) 中且集合互不相同。
- 每一个数字在所有元素中出现次数之和为偶数。
不难发现我们不需要考虑两个序列“本质相同”,因为最后直接除以 (m!) 即可。
考虑 dp。设 (f_i) 表示序列前 (i) 个元素有多少种方案是合法的。那么考虑容斥:
- 无视所有条件,当我们确定了前 (i-1) 个元素(保证两两不同)时,我们一定可以唯一确定第 (i) 个元素,因为我们转化成了有标号的,所以方案数就是 (inom{2^n-1}{i-1}i!)。
- 考虑减去重复的情况,假设第 (i) 个集合与第 (j(j<i)) 个集合完全一致,那么同时删去这两个元素能得到一个长度为 (i-2) 的合法序列。除去这 (i-2) 个元素以外,(i,j) 有 (2^n-1-(i-2)) 种可能,(j) 可以被放在序列 (i-1) 个空隙之间,所以重复的数量是 ((i-1)(2^n-1-(i+1))f_{i-2})
- 还要减去前 (i-1) 个元素已经保证了每一个数字各出现偶数次,这样第 (i) 个元素唯一确定是空集,不合法。方案数显然是 (f_{i-1})。
所以我们得到
[f_i=inom{2^n-1}{i-1}i!-(i-1)(2^n-1-(i+1))f_{i-2}-f_{i-1}
]
组合数递推,其他预处理即可。时间复杂度 (O(n+m))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010,MOD=1e8+7;
int n,m;
ll power,f[N],g[N],fac[N],inv[N];
ll fpow(ll x,ll k)
{
ll ans=1;
for (;k;k>>=1,x=x*x%MOD)
if (k&1) ans=ans*x%MOD;
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
fac[0]=inv[0]=inv[1]=g[0]=f[0]=1;
power=fpow(2,n);
for (int i=1;i<=m;i++)
{
fac[i]=fac[i-1]*i%MOD;
if (i>1) inv[i]=-1LL*MOD/i*inv[MOD%i]%MOD;
g[i]=g[i-1]*(power-i)%MOD*inv[i]%MOD;
f[i]=(g[i-1]*fac[i-1]-f[i-1])%MOD;
if (i>1) f[i]=(f[i]-f[i-2]*(power-1-(i-2))%MOD*(i-1))%MOD;
}
printf("%lld",(f[m]*fpow(fac[m],MOD-2)%MOD+MOD)%MOD);
return 0;
}