【题解】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;
}

浙公网安备 33010602011771号