P3214 [HNOI2011] 卡农

从 $[1,2^n)$ 中选择 $m$ 个互不相同且异或和为 $0$ 的数,求方案数。

$1 \le n,m \le 10^6$。

考虑 DP,记 $f_i$ 为选择 $i$ 个数的方案数。先考虑顺序,这样选择的数不会有顺序的限制,最后再除 m! 得到答案。

发现确定前 $i-1$ 个数就可以通过异或和为 $0$ 的条件得到第 $i$ 个数,那么得到初始方案数 $\binom{2^n-1}{i-1}(i-1)!$。记前 $i-1$ 个数的异或和为 $u$,那么 $i$ 填 $u$。考虑不合法的方案,减去 $u=0$ 和 $u$ 出现两次的方案,剩下的都合法。得到转移式

初值 $f_1=f_2=0$。时间复杂度 $O(m)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<bits/stdc++.h>
using namespace std;

const int N=1e6+10,MOD=1e8+7;
inline int qm(int x,int y=MOD-2){ int res=1; for(;y;y>>=1,x=1ll*x*x%MOD) if(y&1) res=1ll*res*x%MOD; return res; }
inline void mod(int &x){ if(x>=MOD) x-=MOD; if(x<0) x+=MOD; }
#define gc getchar()
#define rd read()
inline int read(){
int x=0,f=0; char c=gc;
for(;c<'0'||c>'9';c=gc) f|=(c=='-');
for(;c>='0'&&c<='9';c=gc) x=(x<<1)+(x<<3)+(c^48);
return f?-x:x;
}

int jc[N],fjc[N];
void init(int n){
jc[0]=1; for(int i=1;i<=n;++i) jc[i]=1ll*jc[i-1]*i%MOD;
fjc[n]=qm(jc[n]); for(int i=n-1;~i;--i) fjc[i]=1ll*fjc[i+1]*(i+1)%MOD;
}

int n,m,pw,A[N],f[N];

int main(){

n=rd,m=rd,pw=qm(2,n),init(m);
for(int i=A[0]=1;i<=m;++i) A[i]=1ll*A[i-1]*(pw-i)%MOD;

f[1]=f[2]=0;
for(int i=3;i<=m;++i){
mod(f[i]=A[i-1]-f[i-1]);
mod(f[i]-=1ll*(pw-1-(i-2))*(i-1)%MOD*f[i-2]%MOD);
}

printf("%d\n", 1ll*f[m]*fjc[m]%MOD);

return 0;
}