[AGC070B] Odd Namori 题解
Description
给定一个含有 \(N\) 个顶点的有根树 \(T\),顶点编号为 \(1\) 到 \(N\)。其中,顶点 \(1\) 为根节点,对于每一个顶点 \(i\)(\(2 \leq i \leq N\)),有 \(p_i\) 为其父节点,且 \(p_i < i\)。
我们定义满足以下条件的有向图 \(G\) 为“好图”:
- 每个顶点的出度都为 1。
- 图中不存在偶数长度的环。
- 对于所有 \(2 \leq i \leq N\) 的 \(i\),\(G\) 中不包含边 \(i \to p_i\)。
计算所有可能的“好图”\(G\) 中 \(2^{\text{环的数量}}\) 的总和对 \(998244353\) 取模的结果。
\(2\leq N\leq 10^5\)。
Solution
首先考虑可以出现偶环和树边怎么计数。
设环数是 \(c\),容易发现 \(2^c\) 这个数可以改成 \((1+1)^c\),这个东西的意义是对于每个环,可以选或者不选,选的权值是 \(1\),每种选择方案的权值是所有选了的环的权值乘积,所有选择方案的权值之和就是 \(2^c\)。
所以直接对于每个 \(k\),计算 \(f(k)\) 表示钦定任意 \(k\) 个环的方案数,\(\sum f(k)\) 就是答案。
现在把不能出现偶环的限制加上。
可以想到给奇偶环分别赋值成 \(1\) 和 \(-1\),那么贡献可以表示成 \((1+1)^{奇环数}(1-1)^{偶环数}\),偶环数不为 \(0\) 的时候贡献一定为 \(0\),否则就是 \(2^{环数}\)。
一个环的权值可以改成 \((-1)^{点数+1}\),所以如果固定了钦定在环里的点数 \(k\),则整个环划分的权值就只跟环数有关了。
经过打表会发现环划分的权值是 \([k\leq 1]\),证明如下。
证明
假设钦定在环上的点为 \(1,2,\ldots,k\),考虑构造双射。
- 如果 \(1\) 和 \(2\) 在同一个环中,映射的方案是把它们所在的环按照 \(1\) 和 \(2\) 两点断开。
- 如果 \(1\) 和 \(2\) 不在同一个环中,映射的方案是把它们所在的环合并。
容易证明上述方案是两两对应的,且对应的一对方案的环数一定差 \(1\),所以当 \(k\geq 2\) 时总权值一定是 \(0\)。
也就是说只需要计算钦定的环一定只能是一个大小为 \(1\) 的环或者没有钦定的环的权值。
最后再把不能出现树边的限制加上。
设钦定在环中的树边数量是 \(d\),则还需要乘上 \((-1)^d\) 的系数。
先固定钦定在环中的点集 \(S\) 以及钦定在 \(S\) 中的树边集合 \(T\),那么把 \(T\) 中的边加上后 \(S\) 的导出子图的每个连通块一定是链,否则连不成环。
由于总权值只关心环数和总点数之和,所以这些链都可以看成单点,就和之前的问题等价了,于是这些钦定的链只能至多有一条。
所以这题可以先枚举钦定的链,计算这条链构成的环的贡献,再乘上链之外的点随便连非树出边的方案数即可。
时间复杂度:\(O(n)\)。
Code
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 1e5 + 5, kMod = 998244353;
int n;
int p[kMaxN], dep[kMaxN];
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
inline int getop(int x) { return (~x & 1) ? 1 : (kMod - 1); }
int calc(int x, int k) {
// x^0 + x^1 + ... + x^k
if (k < 0) return 0;
else if (x == 1) return k + 1;
else return 1ll * sub(qpow(x, k + 1), 1) * qpow(x - 1) % kMod;
}
void dickdreamer() {
std::cin >> n;
dep[1] = 1;
for (int i = 2; i <= n; ++i) {
std::cin >> p[i];
dep[i] = dep[p[i]] + 1;
}
int ans = 1ll * n * qpow(n - 1, n - 1) % kMod;
for (int i = 1; i <= n; ++i) {
// for (int j = 1; j < dep[i]; ++j)
// inc(ans, 1ll * n * qpow(n - 1, n - 1 - j) % kMod);
inc(ans, 1ll * n * qpow(n - 1, n - dep[i]) % kMod * calc(n - 1, dep[i] - 2) % kMod);
inc(ans, qpow(n - 1, n - dep[i]));
}
std::cout << ans << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

浙公网安备 33010602011771号