题解 P3214 【[HNOI2011]卡农】
题解
刚看到题意的时侯很懵,需要蹋下心研读
我是在学校 \(OJ\) 状压 \(DP\) 中做的这道题,所以我就想用二进制来存储不同的集合
题意可以转化成: 从一个 \(\{1,2,3...2^n-1\}\) 的集合中选出 \(m\) 个数,使得这 \(m\) 个数构成一个无序的集合且符合两个条件:
-
所选出的几个子集非空,也就是选出的数非零。
-
所选的 \(m\) 个数其异或和为零。
-
所选的 \(m\) 个数中不能有相同的数
有了简化的题意后可以想到要用 \(DP\) , \(f_i\) 表示选 \(i\) 个数的合法情况数。因为要求异或和为零,所以前 \(i-1\) 个数确定后则第 \(i\) 个数也就确定了,所以此时有序方案数为 \(A_{2^n-1}^{i-1}\) 。
但是这时会有不合法的情况,所以要进行处理:
-
前 \(i-1\) 个数异或和为 \(0\) ,所以前 \(i-1\) 个数构成了合法情况,此时第 \(i\) 个数为 \(0\) ,不合法。所以此时不合法情况数即为 \(f_{i-1}\)
-
前 \(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;
}