The 2024 ICPC Asia East Continent Online Contest (II) K.Match

题面

K.Match

给定长度为 \(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;
}