[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
", 1ll * f[m] * ksm(fac, mod - 2) % mod);
    return 0;
}
原文地址:https://www.cnblogs.com/asuldb/p/11189772.html