题面
给定长度为 \(n\) 的两个序列 \(a\) 和 \(b\),当且仅当 \(a_i \oplus b_j \ge k\) 时,\(a_i\) 与 \(b_j\) 连一条双向边,其中 \(\oplus\) 表示 XOR 运算。对于 \([1, n]\) 范围内的每个 \(x\),计算大小为 \(x\) 的匹配数的个数,结果对 \(998244353\) 取模。
题解
考虑两个序列 \(A, B\) 如何计算,不妨考虑从 \(k\) 的最高位开始枚举,对于 \(k\) 的下一个位,当前的合法 \(A, B\) 一定,我们为了方便讨论,按当前位为 \(0/1\) 分为 \(A_0, A_1, B_0, B_1\)。
序列 \(A_0, A_1, B_0, B_1\) 的大小是单调不增的,为了方便讨论,我们规定 \(|S|\) 表示集合 \(S\) 的大小,\(\leftrightarrow\) 表示两者有边。
-
\(k\) 的下一位为 \(1\),序列 \(A, B\) 合法的条件是 \(A_0 \leftrightarrow B_1\) 和 \(A_1 \leftrightarrow B_0\)。
- 记 \(A_0 \leftrightarrow B_1\) 的方案数为 \(f_i\),\(A_1 \leftrightarrow B_0\) 的方案数为 \(g_i\),容易发现,答案为 \(f\) 与 \(g\) 的一个卷积,即 \(ans_{i + j} = f_i \times g_j\)。
-
\(k\) 的下一位为 \(0\),序列 \(A, B\) 的合法条件为 \(A_0 \leftrightarrow B_0\) 和 \(A_1 \leftrightarrow B_1\),同时对于 \(A_0 \leftrightarrow B_1\) 和 \(A_1 \leftrightarrow B_0\) 一定成立,我们需要考虑如何将两者答案合并。
-
记 \(A_0 \leftrightarrow B_0\) 的方案数为 \(f_i\),\(A_1 \leftrightarrow B_1\) 的方案数为 \(g_i\),\(A_0 \leftrightarrow B_1\) 的方案数为 \(h_i\),\(A_1 \leftrightarrow B_0\) 的方案数为 \(t_i\)。
-
考虑 \(A_0 \leftrightarrow B_1\) 和 \(A_1 \leftrightarrow B_0\) 的任选性,任选 \(i\) 个对其随机排列,容易看出 \(h_i = i!\binom{|A_0|}{i}\binom{|B_1|}{i}\),\(t_i = i!\binom{|A_1|}{i}\binom{|B_0|}{i}\)。
-
\(A_0 \leftrightarrow B_0\) 和 \(A_1 \leftrightarrow B_1\) 的组合意义同 \(k\) 取 \(1\) 时的操作,对 \(f\) 与 \(g\) 做卷积得到 \(S_{i+j} = f_i \times g_j\)。
-
-
为了将 \(h, t\) 与 \(S\) 合并,枚举三个集合分别选择了 \(i, j, l\) 个匹配。
-
对每组 \((i, j)\),去除 \(A_0 \leftrightarrow B_0\) 使用的 \(i\) 对,\(A_1 \leftrightarrow B_1\) 使用的 \(j\) 对,每次重新计算一组 \(S\),其中 \(h_u = u!\binom{|A_0| - i}{u}\binom{|B_1| - j}{u}\),\(t_u = u!\binom{|A_1| - j}{u}\binom{|B_0| - i}{u}\)
-
先卷 \(h\) 和 \(t\) 得到 \(S\),再把三者卷起来有 \(ans_{i + j + l} = h_i \times t_j \times S_l\)。
-
-
-
考虑边界情况,定义 \(\operatorname{solve(A, B, lev)}\) 为递归计算的 \(A, B\) 序列,\(lev\) 为当前考虑的位。
-
当 \(|A| = 0\) 或 \(|B| = 0\) 时,没有可选项数,\(ans_0 = 1\),\(ans_i = 0(i > 0)\)。
-
当 \(lev = -1\) 时,说明前面的位被考虑完毕了,\(A, B\) 可以任意组合,计算 \(ans_i = i!\binom{|A|}{i}\binom{|B|}{i}\)。
-
否则将 \(A, B\) 按当前位分解为 \(A_0, A_1, B_0, B_1\) 对下一个 \(lev\) 讨论。
-
按照题意分析复杂度,对于 \(k\) 的位为 \(0\) 时,计算一次 \(S\) 的复杂度是卷积 \(O(n^2)\) 的,卷一次 \(ans_{i + j + l}\) 需要枚举 \(i, j\),并在当前 \(i, j\) 下卷出一个 \(S\),时间复杂度是 \(O(n^4)\) 的,可以优化做到 \(O(n^3\log{n})\),但是原算法时间复杂度很小,因为 \(|A|, |B|\) 的大小是单调不增的,所以跑不满。
总体时间复杂度 \(O(n^4\log{k})\),代码如下。
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<ll> Poly;
const int N = 405, mod = 998244353, maxn = 400;
ll n, k;
ll frac[N], inv[N];
ll ksm(ll a, ll k)
{
ll res = 1;
while (k)
{
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
void init()
{
frac[0] = inv[0] = 1;
for (int i = 1; i <= maxn; i ++ ) frac[i] = frac[i - 1] * i % mod;
inv[maxn] = ksm(frac[maxn], mod - 2);
for (int i = maxn - 1; i; i -- ) inv[i] = inv[i + 1] * (i + 1) % mod;
}
ll C(ll x, ll y)
{
if (x < y || y < 0) return 0;
return frac[x] * inv[y] % mod * inv[x - y] % mod;
}
Poly Covolution(Poly x, Poly y)
{
Poly tmp(x.size() + y.size() - 1);
for (int i = 0; i < x.size(); i ++ )
for (int j = 0; j < y.size(); j ++ )
(tmp[i + j] += x[i] * y[j]) %= mod;
return tmp;
}
Poly calc(int x, int y)
{
Poly tmp(min(x, y) + 1);
for (int i = 0; i <= min(x, y); i ++ ) tmp[i] = C(x, i) * C(y, i) % mod * frac[i] % mod;
return tmp;
}
Poly solve(Poly x, Poly y, int lev)
{
if (x.empty() || y.empty()) return Poly(1, 1);
if (lev == -1) return calc(x.size(), y.size());
Poly X[2], Y[2];
for (auto i : x) X[i >> lev & 1].push_back(i);
for (auto i : y) Y[i >> lev & 1].push_back(i);
if (k >> lev & 1) return Covolution(solve(X[1], Y[0], lev - 1), solve(X[0], Y[1], lev - 1));
Poly tmp[2], res(min(x.size(), y.size()) + 1);
tmp[0] = solve(X[0], Y[0], lev - 1), tmp[1] = solve(X[1], Y[1], lev - 1);
for (int i = 0; i < tmp[0].size(); i ++ )
for (int j = 0; j < tmp[1].size(); j ++ )
{
Poly c = Covolution(calc(X[0].size() - i, Y[1].size() - j), calc(X[1].size() - j, Y[0].size() - i));
for (int l = 0; l < c.size(); l ++ ) (res[i + j + l] += tmp[0][i] * tmp[1][j] % mod * c[l]) %= mod;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
init();
cin >> n >> k;
Poly a(n), b(n);
for (int i = 0; i < n; i ++ ) cin >> a[i];
for (int i = 0; i < n; i ++ ) cin >> b[i];
Poly ans = solve(a, b, 60);
ans.resize(n + 1);
for (int i = 1; i <= n; i ++ ) cout << ans[i] << "\n";
return 0;
}