[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;
}
posted @ 2025-11-17 16:01  下蛋爷  阅读(14)  评论(0)    收藏  举报