
「CF1336E2」Chiori and Doll Picking (hard version)
给定 个整数 ,在 的范围内。对于 ,求选出一个子集使得异或和的二进制表示有 个 的方案数。
。
题解
0x01
定义:
- 表示 的二进制表示下 的个数
对于线性基 ,定义:
- 表示 张成的向量空间
对于此题,定义
- 为由题中给定数得到的线性基
首先你已经会了一个 的暴力,下文我们介绍一种 的算法,就可以通过分治在 的时间复杂度内通过本题。
0x02
由线性基的基本性质,可以得到:
在此基础上枚举 有
由于是按位相乘,考虑方程 的实根仅有
故 。
0x03
让我们再回到 运算本身的意义:
如果存在 使得 ,则 只能为 。
和 运算满足结合律:
可以通过把 理解为二进制按位乘法, 理解为二进制不进位加法来证明。
故我们只需检验 中的每个基底而非 即可判断这一位的值。
0x04
定义 的正交线性基为 ,使得对于所有 ,满足 是偶数,且 。
根据前面的引理,有
一种简单的正交线性基构造方式是
用高斯消元整理关键位,旋转右上角到左下角得到。
证明可以考虑图中圈出矩形的左上角和右上角一定为 ,而两向量的异或的 为偶数,那么左下角和右上角的数要么全为 ,要么全为 。
0x05
知道了正交线性基怎么求,如何计算答案呢?
考虑用 表示答案统计:
其中 表示 。
其中:
由于 ,而 中的元素只有 个。故通过暴力得到 ,即可通过组合数计算得 。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 60, mod = 998244353;
int n, m, k, t, c[N];
long long f[N], g[N];
struct z {
int x;
z(int x = 0) : x(x) {}
friend inline z operator*(z a, z b) { return (long long)a.x * b.x % mod; }
friend inline z operator-(z a, z b) { return (a.x -= b.x) < 0 ? a.x + mod : a.x; }
friend inline z operator+(z a, z b) { return (a.x += b.x) >= mod ? a.x - mod : a.x; }
} p[N], q[N], fac[N], ifac[N];
inline z C(int n, int m) { return n < m ? 0 : fac[n] * ifac[m] * ifac[n - m]; }
inline z fpow(z a, int b) {
z s = 1;
for (; b; b >>= 1, a = a * a)
if (b & 1) s = s * a;
return s;
}
inline void insert(long long x) {
for (int i = m - 1; i >= 0; i--)
if ((x >> i) & 1) {
if (f[i]) x ^= f[i];
else {
f[i] = x;
return;
}
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
long long x;
cin >> x, insert(x);
}
for (int i = 0; i < m; i++)
for (int j = i + 1; j < m; j++)
if ((f[j] >> i) & 1) f[j] ^= f[i];
for (int i = 0; i < m; i++)
if (f[i]) c[i] = k++;
for (int i = 0; i < m; i++)
if (!f[i]) c[i] = k + (t++);
for (int i = 0; i < m; i++)
if (f[i])
for (int j = 0; j < m; j++)
if ((f[i] >> j) & 1) g[c[i]] |= 1ll << c[j];
for (int i = 0; i < k; i++)
for (int j = k; j < m; j++)
if ((g[i] >> j) & 1) g[j] |= 1ll << i;
for (int i = k; i < m; i++) g[i] |= 1ll << i;
if (k <= ((m + 1) >> 1)) {
function<void(int, long long)> dfs = [&](int i, long long s) {
if (i >= k) {
p[__builtin_popcountll(s)].x++;
return;
}
dfs(i + 1, s), dfs(i + 1, s ^ g[i]);
};
dfs(0, 0ll);
} else {
function<void(int, long long)> dfs = [&](int i, long long s) {
if (i >= m) {
q[__builtin_popcountll(s)].x++;
return;
}
dfs(i + 1, s), dfs(i + 1, s ^ g[i]);
};
dfs(k, 0ll);
fac[0] = ifac[0] = ifac[1] = 1;
for (int i = 1; i <= m; i++) fac[i] = fac[i - 1] * i;
for (int i = 2; i <= m; i++) ifac[i] = (mod - mod / i) * ifac[mod % i];
for (int i = 1; i <= m; i++) ifac[i] = ifac[i - 1] * ifac[i];
for (int c = 0; c <= m; c++)
for (int d = 0; d <= m; d++)
for (int i = 0; i <= c; i++) {
p[c] = p[c] + fpow(2, mod - 1 + k - m) * q[d] * (i & 1 ? mod - 1 : 1) * C(d, i) * C(m - d, c - i);
}
}
for (int i = 0; i <= m; i++) cout << (p[i] * fpow(2, n - k)).x << " \n"[i == m];
}
Comments