【洛谷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) 个元素有多少种方案是合法的。那么考虑容斥:

  1. 无视所有条件,当我们确定了前 (i-1) 个元素(保证两两不同)时,我们一定可以唯一确定第 (i) 个元素,因为我们转化成了有标号的,所以方案数就是 (inom{2^n-1}{i-1}i!)
  2. 考虑减去重复的情况,假设第 (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})
  3. 还要减去前 (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;
}