【题目链接】
【思路要点】
- 记 x i x_i xi 表示点 i i i 被选中时,点 i i i 所在的连通块共被操作了多少次,则有
E ( A n s ) = E ( ∑ i = 1 N x i ) = ∑ i = 1 N E ( x i ) E(Ans)=E(\sum_{i=1}^{N} x_i)=\sum_{i=1}^{N} E(x_i) E(Ans)=E(i=1∑Nxi)=i=1∑NE(xi)- 考虑计算 x i x_i xi ,记 P i , j P_{i,j} Pi,j 表示在点 j j j 被选中之前 i i i 与 j j j 始终连通的概率,则有
E ( x i ) = ∑ j = 1 N P i , j E(x_i)=\sum_{j=1}^{N}P_{i,j} E(xi)=j=1∑NPi,j- 若给定的仙人掌是一棵树,那么记 i i i 到 j j j 路径上的点数为 d i s t i , j dist_{i,j} disti,j ,有 P i , j = 1 d i s t i , j P_{i,j}=\frac{1}{dist_{i,j}} Pi,j=disti,j1 。
- 建立仙人掌的圆方树,那么 i i i 到 j j j 路径上经过的圆点不能比 j j j 先被选中,且经过的每一个环由必经点分割而成的两部分不能均有点比 j j j 先被选中。
- 记 i i i 到 j j j 路径上走过环的总点数为 c n t cnt cnt ;在这些点中选出一个大小为 x x x 的集合,使得删去集合中的点后 i i i 到 j j j 依然连通的方案数为 d p x dp_x dpx 。考虑枚举 j j j 被选中之前先被选中的点数 k k k ,则有 P i , j = ∑ k = 0 c n t − 1 1 k + 1 × d p k ( c n t k ) P_{i,j}=\sum_{k=0}^{cnt-1}\frac{1}{k+1}\times\frac{dp_k}{\binom{cnt}{k}} Pi,j=k=0∑cnt−1k+11×(kcnt)dpk
- 在仙人掌的圆方树上进行上述 d p dp dp ,可以在 O ( N 2 ) O(N^2) O(N2) 的时间内得出特定的 E ( x i ) E(x_i) E(xi) 。
- 时间复杂度 O ( N 3 ) O(N^3) O(N3) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 405; const int MAXP = 805; const int P = 998244353; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, m, tot, ans, size[MAXN], rnk[MAXN][MAXN]; int timer, dfn[MAXN], low[MAXN], dp[MAXN][MAXN]; int binom[MAXN][MAXN], invb[MAXN][MAXN]; int top, Stack[MAXN], inv[MAXN]; vector <int> a[MAXN], t[MAXP]; void update(int &x, int y) { x += y; if (x >= P) x -= P; } int power(int x, int y) { if (y == 0) return 1; int tmp = power(x, y / 2); if (y % 2 == 0) return 1ll * tmp * tmp % P; else return 1ll * tmp * tmp % P * x % P; } void work(int pos, int fa, int cnt) { if (pos <= n) { for (int i = 0; i <= cnt; i++) update(ans, 1ll * dp[pos][i] * invb[cnt + 1][i] % P * inv[cnt + 1 - i] % P); for (auto x : t[pos]) if (x != fa) work(x, pos, cnt); } else { int now = pos - n; static int tmp[MAXN][MAXN]; memcpy(tmp[1], dp[fa], sizeof(dp[fa])); for (int i = 2; i <= size[now]; i++) { memset(tmp[i], 0, sizeof(tmp[i])); for (int j = 0; j <= cnt + i; j++) { tmp[i][j] = tmp[i - 1][j]; if (j) update(tmp[i][j], tmp[i - 1][j - 1]); } } for (auto x : t[pos]) if (x != fa) { int dis = abs(rnk[now][fa] - rnk[now][x]); for (int i = 0; i <= cnt + size[now] - 1; i++) { dp[x][i] = tmp[dis][i] + tmp[size[now] - dis][i] - dp[fa][i]; if (dp[x][i] >= P) dp[x][i] -= P; else if (dp[x][i] < 0) dp[x][i] += P; } } for (auto x : t[pos]) if (x != fa) work(x, pos, cnt + size[now] - 1); } } void dfs(int pos, int fa) { Stack[++top] = pos; low[pos] = dfn[pos] = ++timer; for (auto x : a[pos]) if (!dfn[x]) { dfs(x, pos); chkmin(low[pos], low[x]); if (low[x] == dfn[pos]) { int tmp = 0; tot++; while (tmp != x) { tmp = Stack[top--]; t[tot + n].push_back(tmp); t[tmp].push_back(tot + n); rnk[tot][tmp] = ++size[tot]; } t[tot + n].push_back(pos); t[pos].push_back(tot + n); rnk[tot][pos] = ++size[tot]; } } else chkmin(low[pos], dfn[x]); } int main() { read(n), read(m); for (int i = 0; i <= n; i++) { inv[i] = power(i, P - 2); binom[i][0] = invb[i][0] = 1; for (int j = 1; j <= i; j++) { binom[i][j] = (binom[i - 1][j] + binom[i - 1][j - 1]) % P; invb[i][j] = power(binom[i][j], P - 2); } } for (int i = 1; i <= m; i++) { int x, y; read(x), read(y); a[x].push_back(y); a[y].push_back(x); } dfs(1, 0); for (int i = 1; i <= n; i++) { memset(dp, 0, sizeof(dp)); dp[i][0] = 1, work(i, 0, 0); } writeln(ans); return 0; }