省队集训的时候遇到一个加强版,推了很久的集合容斥之后自闭了
考虑递推,设(f_i)表示选择(i)个([1,2^n-1])里的数,且异或和为(0)的排列数,答案就是(frac{f[m]}{m!})
考虑转移,发现不好转移,于是考虑在转移的时候正难则反一下
我们现在需要求(f_i),由于现在的异或和是(0),那么前(i-1)个异或起来一定不是(0),前(i-1)个有(A_{2^n-1}^{i-1})种方案,减掉异或和为(0)的(f_{i-1})种,那么剩下的异或和肯定不是(0)
现在直接选择一个当前的异或和使得当前的异或和为(0)就好了,但是这样可能就会出现最后选择的这个数在之前已经出现过了,于是我们还要消去一些东西
考虑连续选择两个相同的数异或和不变,于是考虑从(f_{i-2})转移
我们直接从(f_{i-2})的基础上选择两个一样的数,这样的选择有(2^n-1-(i-2))种,其中一个需要在最后一个,另外的一个需要插到前面的(i-2)个数里,也就是(i-1)个空位中
于是(f_i=A_{2^n-1}^{i-1}-f_{i-1}-(i-1)(2^n-i+1)f_{i-2})
代码
#include <bits/stdc++.h>
#define re register
const int mod = 100000007;
int n, m, now, k, pw, fac;
int f[1000005];
inline int ksm(int a, int b) {
int S = 1;
for (; b; b >>= 1, a = 1ll * a * a % mod)
if (b & 1)
S = 1ll * S * a % mod;
return S;
}
int main() {
scanf("%d%d", &n, &m);
pw = 1;
f[1] = 0, f[0] = 1;
pw = ksm(2, n) - 1;
now = pw, k = pw, fac = 1;
for (re int i = 2; i <= m; i++) {
f[i] =
((now - f[i - 1] + mod) % mod - 1ll * f[i - 2] * (i - 1) % mod * (k - i + 2) % mod + mod) % mod;
pw--;
now = 1ll * now * pw % mod;
fac = 1ll * fac * i % mod;
}
printf("%d
", 1ll * f[m] * ksm(fac, mod - 2) % mod);
return 0;
}