【题解】P14561 [CXOI2025] 我常常追忆过去

Algorithm 0

输出样例,期望得分 \(0\) 分。

Algorithm 1

竞赛图有很多优美的性质,比如说:

  • 竞赛图缩点之后形成一条链。

通过这个性质可以得出下面的结论:对于一张给定的竞赛图而言,竞赛图的 SCC 数量为 \(\sum\limits_{i=1}^n{[\sum\limits_{j=1}^id_j'=\binom i2]}\),其中 \(d'\) 数组是图上所有点的入度 \(\deg\) 升序排序得到的新数组。

直接暴力枚举竞赛图上每条边的朝向,时间复杂度为 \(O(2^{n^2})\),期望得分 \(0\) 分。

Algorithm 2

暴力枚举朝向还是太?了,有没有更正常的做法?

考虑枚举划分的位置 \(i\)(即对给定的 \(i\) 上面艾弗森括号内取等),计数该条件成立时的概率。考虑这个条件成立的另一个定义,其实就是要求该竞赛图上的某 \(i\) 个点组成的集合 \(L\) 连向另外 \(n-i\) 个点组成的集合 \(R\) 的边都是由 \(L\) 集合单向连向 \(R\) 集合的。很显然这个条件成立的概率就是 \(2^{-i(n-i)}\)。而从 \(n\) 个点中选 \(i\) 个点划分出 \(L,R\) 两个集合的方案数则是 \(\binom ni\)

因此 \(n\) 个点组成的竞赛图 SCC 数量的期望就是:

\[1+\sum\limits_{i=1}^{n-1}\binom ni2^{-i(n-i)} \]

可以 \(O(n^2)\) 对每个 \(n=1\ldots m\) 求答案。

Algorithm 3

能不能继续优化?

前置知识:NTT,Chirp-Z Transform(CZT)

套路的对上面的式子拆组合数,得到:

\[\begin{aligned} &1+\sum\limits_{i=1}^{n-1}\binom ni2^{-i(n-i)}\\ =&1+\sum\limits_{i=1}^{n-1}\frac{n!}{i!(n-i)!}2^{-i(n-i)}\\ =&1+n!\sum\limits_{i=1}^{n-1}\frac{2^{-i(n-i)}}{i!(n-i)!}\\ =&1+n!\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{i(n-i)}}{i!(n-i)!}\\ =&1+n!\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{\binom n2-\binom i2-\binom{n-i}2}}{i!(n-i)!}\\ =&1+n!(\frac12)^{\binom n2}\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{\binom i2-\binom{n-i}2}}{i!(n-i)!}\\ =&1+n!(\frac12)^{\binom n2}\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{-\binom i2}}{i!}\times\frac{(\frac12)^{-\binom{n-i}2}}{(n-i)!}\\ \end{aligned} \]

后半部分的和式是一个等和卷积的形式,可以用 NTT 做到 \(O(n\log n)\) 求解。总时间复杂度为 \(O(n\log n)\) 可以通过该题。

// #pragma GCC optimize(3, "Ofast", "inline", "unroll-loops")
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2200010;
const int mod = 998244353;
const int inf = 1e18;

char s[N];

using ull = unsigned long long;
const ull base = 13331;

namespace Luminescent
{
    struct DSU
    {
        int fa[N];
        inline DSU() { iota(fa, fa + N, 0); }
        inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
        inline int merge(int x, int y)
        {
            x = find(x), y = find(y);
            if (x != y)
                return fa[x] = y, 1;
            return 0;
        }
    };
    inline void add(int &x, int v) { x = (x + v) % mod; }
    inline void sub(int &x, int v) { x = (x - v + mod) % mod; }
    inline int power(int a, long long b, int c)
    {
        int sum = 1;
        while (b)
        {
            if (b & 1)
                sum = 1ll * sum * a % c;
            a = 1ll * a * a % c, b >>= 1;
        }
        return sum;
    }
    inline int inversion(int x) { return power(x, mod - 2, mod); }
    inline int varphi(int x)
    {
        int phi = 1;
        for (int i = 2; i * i <= x; ++i)
            if (x % i == 0)
            {
                phi *= (i - 1);
                x /= i;
                while (x % i == 0)
                    phi *= i, x /= i;
            }
        if (x > 1)
            phi *= (x - 1);
        return phi;
    }
} using namespace Luminescent;

namespace Poly
{
    const int g = 3;
    int rev[N * 4];
    void ntt(int *a, int n, int mode)
    {
        int bit = 1;
        while ((1 << bit) < n)
            ++bit;
        for (int i = 0; i < n; ++i)
        {
            rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
            if (i < rev[i])
                swap(a[i], a[rev[i]]);
        }
        for (int l = 2; l <= n; l <<= 1)
        {
            int x = power(g, (mod - 1) / l, mod);
            if (mode == 1)
                x = inversion(x);
            for (int i = 0; i < n; i += l)
            {
                int v = 1;
                for (int j = 0; j < l / 2; ++j, v = v * x % mod)
                {
                    int v1 = a[i + j], v2 = a[i + j + l / 2] * v % mod;
                    a[i + j] = (v1 + v2) % mod, a[i + j + l / 2] = (v1 - v2 + mod) % mod;
                }
            }
        }
    }
    // calc convolution: c[i] = \sum\limits_{j=0}^i (a[j] * b[i - j])
    void convolution(int *a, int n, int *b, int m, int *c)
    {
        int tn = n, tm = m;
        n = n + m + 2;
        while (__builtin_popcount(n) > 1)
            ++n;
        // cerr << "n = " << n << '\n';
        for (int i = tn + 1; i <= n + 1; ++i)
            a[i] = 0;
        for (int i = tm + 1; i <= n + 1; ++i)
            b[i] = 0;
        ntt(a, n, 0), ntt(b, n, 0);
        for (int i = 0; i < n; ++i)
            c[i] = a[i] * b[i] % mod;
        ntt(c, n, 1);
        const int inv_n = inversion(n);
        for (int i = 0; i <= n + m; ++i)
            c[i] = c[i] * inv_n % mod;
    }
}

namespace Loyalty
{
    int fac[N], inv[N], ifac[N];
    int A[N << 2], B[N << 2], C[N << 2];
    inline void init() { }
    inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc)
    {
        int m, op;
        cin >> m >> op;
        for (int i = 0; i < 2; ++i)
            fac[i] = inv[i] = ifac[i] = 1;
        for (int i = 2; i < N; ++i)
        {
            fac[i] = fac[i - 1] * i % mod;
            inv[i] = mod - inv[mod % i] * (mod / i) % mod;
            ifac[i] = ifac[i - 1] * inv[i] % mod;
        }
        const int inv_2 = mod + 1 >> 1;
        for (int i = 0; i <= m; ++i)
            A[i] = B[i] = inversion(power(inv_2, i * (i - 1) / 2, mod)) * ifac[i] % mod;
        Poly::convolution(A, m, B, m, C);
        if (op == 1)
        {
            for (int i = 1; i <= m; ++i)
                cout << (C[i] * power(inv_2, i * (i - 1) / 2, mod) % mod * fac[i] - 1 + mod) % mod << '\n';
        }
        else
        {
            int res = 0;
            for (int i = 1; i <= m; ++i)
                res ^= ((C[i] * power(inv_2, i * (i - 1) / 2, mod) % mod * fac[i] - 1 + mod) % mod);
            cout << res << '\n';
        }
    }
}

signed main()
{
    // freopen("25.in", "r", stdin);
    // freopen("25.out", "w", stdout);
    cin.tie(0)->sync_with_stdio(false);
    cout << fixed << setprecision(15);
    int T = 1;
    // cin >> T;
    Loyalty::init();
    for (int ca = 1; ca <= T; ++ca)
        Loyalty::main(ca, T);
    return 0;
}
posted @ 2026-01-31 19:03  0103abc  阅读(3)  评论(0)    收藏  举报