【清华集训 2014】玛里苟斯(组合计数 + 线性基)

题目链接:【清华集训 2014】玛里苟斯
推荐博客:【BZOJ 3811】玛里苟斯:线性基(详细证明)

首先想到将 k k 分类讨论。

k=1 k = 1 时,我们考虑每一位的贡献。若有至少一个数第 i i 位为 1 1 ,则对答案的贡献为 valuei2 v a l u e i 2

k=2 k = 2 时,发现每个异或和的平方为 ij2i+jbitibitj ∑ i ∑ j 2 i + j b i t i b i t j 。那么考虑第 i i 位和第 j j 位的积的期望值。如果所有的数中,第 i i 位和第 j j 位均相等且非全零,那么参考 k=1 k = 1 的情况,期望为 12 1 2 ;否则,第 i i 位为 1 1 的概率为 12 1 2 ,第 j j 位为 1 1 的概率为 12 1 2 i×j i × j 1 1 的概率为 14 1 4

k3 k ≥ 3 时, 由于答案不超过 263 2 63 ,所以每个数不超过 221 2 21 ,所以线性基个数不超过 21 21 ,则可以暴力枚举异或和来计算答案了。注意精度问题。

我怀疑我学了假的线性基模版···

#include 
#include 
const int maxn = 100005;
typedef unsigned long long ull;
int n, m, k;
ull a[maxn], base[maxn], b[maxn];
void solve1() {
    ull ans = 0;
    for (int i = 1; i <= n; i++) {
        ans |= a[i];
    }
    printf("%llu", ans >> 1);
    if (ans & 1) {
        printf(".5");
    }
    putchar('\n');
}
void solve2() {
    ull ans = 0, res = 0;
    for (int i = 32; i >= 0; i--) {
        for (int j = 32; j >= 0; j--) {
            bool flag0 = 0, flag1 = 0, flag = 0;
            for (int k = 1; k <= n; k++) {
                flag0 |= a[k] >> i & 1;
                flag1 |= a[k] >> j & 1;
                flag |= (a[k] >> i & 1) != (a[k] >> j & 1);
            }
            if (!flag0 || !flag1) {
                continue;
            }
            if (i + j - flag - 1 < 0) {
                res++;
            } else {
                ans += 1ull << (i + j - flag - 1);
            }
        }
    }
    ans += res >> 1;
    printf("%llu", ans);
    if (res & 1) {
        printf(".5");
    }
    putchar('\n');
}
void solve3() {
    ull ans = 0, res = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 22; j >= 0; j--) {
            if (a[i] >> j & 1) {
                if (base[j]) {
                    a[i] ^= base[j];
                } else {
                    base[j] = a[i];
                    b[++m] = a[i];
                    break;
                }
            }
        }
    }
    for (int i = 0; i < 1 << m; i++) {
        ull val = 0;
        for (int j = 1; j <= m; j++) {
            if (i >> (m - j) & 1) {
                val ^= b[j];
            }
        }
        ull a = 0, b = 1;
        for (int j = 1; j <= k; j++) {
            a *= val, b *= val;
            a += b >> m, b &= (1 << m) - 1;
        }
        ans += a, res += b;
        ans += res >> m, res &= (1 << m) - 1;
    }
    printf("%llu", ans);
    if (res) {
        printf(".5");
    }
    putchar('\n');
}
int main() {
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%llu", a + i);
    }
    if (k == 1) {
        solve1();
    } else if (k == 2) {
        solve2();
    } else {
        solve3();
    }
    return 0;
}

你可能感兴趣的:(线性基,组合计数)