Loading

题解 P3214 【[HNOI2011]卡农】

题解

刚看到题意的时侯很懵,需要蹋下心研读

我是在学校 \(OJ\) 状压 \(DP\) 中做的这道题,所以我就想用二进制来存储不同的集合

题意可以转化成: 从一个 \(\{1,2,3...2^n-1\}\) 的集合中选出 \(m\) 个数,使得这 \(m\) 个数构成一个无序的集合且符合两个条件:

  1. 所选出的几个子集非空,也就是选出的数非零。

  2. 所选的 \(m\) 个数其异或和为零。

  3. 所选的 \(m\) 个数中不能有相同的数

有了简化的题意后可以想到要用 \(DP\)\(f_i\) 表示选 \(i\) 个数的合法情况数。因为要求异或和为零,所以前 \(i-1\) 个数确定后则第 \(i\) 个数也就确定了,所以此时有序方案数为 \(A_{2^n-1}^{i-1}\)

但是这时会有不合法的情况,所以要进行处理:

  1. \(i-1\) 个数异或和为 \(0\) ,所以前 \(i-1\) 个数构成了合法情况,此时第 \(i\) 个数为 \(0\) ,不合法。所以此时不合法情况数即为 \(f_{i-1}\)

  2. \(i-1\) 个数中有与第 \(i\) 个数相同的,设这个数为第 \(j\) 个,那么除去第 \(j\) 个和第 \(i\) 个数,剩余的即为合法的,为 \(f_{i-2}\) ,此时 \(j\)\(2^n-1-(i+2)\) 种,同时 \(j\) 可以有 \(i-1\) 个位置,所以不合法情况数为 \(f_{i-2}×(2^n-1-(i+2))×(i-1)\)

所以我们可以列出总转移方程:

\[f(x)=\left\{ \begin{aligned} &f_i=A_{2^n-1}^{i-1} \\ &f_i =f_i-f_{i-1}\\ &f_i=f_i-f_{i-2}×(2^n-1-(i+2))×(i-1) \end{aligned} \right. \]

注意,因为我们求的是有序的排列,所以最后还需要除以 \(m!\) ,借助逆元。

\(AC \kern 0.4emCODE:\)

#include<bits/stdc++.h>
#define ri register int
#define p(i) ++i
#define int long long
using namespace std;
const int MOD=1e8+7,N=1e6+10;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int f[N],a[N],n,m,mu;
inline int qpow(int x,int y) {
	int ans=1;
	while(y) {
		if (y&1) ans=ans*x%MOD;
		x=x*x%MOD;
		y>>=1;
	}
	return ans;
}
signed main() {
	n=read(),m=read();
	int tot=qpow(2,n)-1;
	a[0]=f[0]=1;//初始化可以自己手动模拟一下
	for (ri i(1);i<=m;p(i)) a[i]=a[i-1]*((tot-i+1+MOD)%MOD)%MOD;
	for (ri i(2);i<=m;p(i)) {
		f[i]=a[i-1];
		f[i]-=f[i-1];
		f[i]=(f[i]-f[i-2]*(tot-i+2)%MOD*(i-1)%MOD+MOD)%MOD;//记得处理正负
	}
	int k=m;mu=m;
	while(--k) {
		mu=mu*k%MOD;
	}
	printf("%lld\n",f[m]*qpow(mu,MOD-2)%MOD);
	return 0;
}
posted @ 2021-01-30 12:18  ナンカエデ  阅读(63)  评论(0编辑  收藏  举报