[HNOI2011]卡农

题目

省队集训的时候遇到一个加强版,推了很久的集合容斥之后自闭了

考虑递推,设\(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\n", 1ll * f[m] * ksm(fac, mod - 2) % mod);
    return 0;
}
posted @ 2019-07-15 16:39  asuldb  阅读(145)  评论(0编辑  收藏  举报