【洛谷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,m\leq 10^6\)。
思路
理一下题目内容发现我们需要求出满足以下几个条件的序列数量:
- 序列中每一个元素所表示的集合元素均在 \([1,n]\) 中且集合互不相同。
- 每一个数字在所有元素中出现次数之和为偶数。
不难发现我们不需要考虑两个序列“本质相同”,因为最后直接除以 \(m!\) 即可。
考虑 dp。设 \(f_i\) 表示序列前 \(i\) 个元素有多少种方案是合法的。那么考虑容斥:
- 无视所有条件,当我们确定了前 \(i-1\) 个元素(保证两两不同)时,我们一定可以唯一确定第 \(i\) 个元素,因为我们转化成了有标号的,所以方案数就是 \(\binom{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=\binom{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;
}