省流:本做法最终时间复杂度 \(O(n log^2 n)\),空间复杂度 \(O(n)\)。
首先,这道题有一个最难的地方,就是我可以一直选到我已经染色的点,所以操作次数是没有上限的。
既然没有上限不好想,根据雷氏三定理发明者雷mini的著名论断:"当你发现没有思路时,不妨考虑考虑生成函数。"
于是,我们设我们在第 \(T\) 次操作时停止,钦定在 \(t\) 次操作后我们还不是点覆盖,那么我们就确定了一个操作的上下界,则此时的期望就为 \(\mathbb{E}T=\sum_{t=0}^{\infty} P(T>t)\)。
根据容斥原理,对于任意一个大小为 \(k\) 的白色点集 \(S\),前 \(t\) 次操作恰好出现 \(S\) 中所有点(且 \(S\) 之外的点一次也未出现)的概率为:\(P(S)=\sum_{m=0}^{k}(-1)^{k-m}\binom{k}{m}\left(\frac{m}{n}\right)^t\)。
因为次数实际无上限,所以要对 \(t\) 从 \(0\) 到 \(\infty\) 求和(注意 \(m = 0\) 时求和为 \(1\),\(m > 0\) 时收敛于 \(\frac{n}{n-m}\)),可得:\(\sum_{t=0}^{\infty}P(S)=\sum_{m=0}^{k}(-1)^{k-m}\binom{k}{m}\frac{n}{n-m} =\frac{n}{(n-k)\binom{n}{k}}\)。
为什么呢?我们来推导一下:
我们注意到:对于固定的 \(k\),构造这样一个有理函数:\(F(x)=\frac{k!}{x(x-1)(x-2)\cdots(x-k)}\),它的分母是连续整数乘积。这种有理函数可以拆成若干个"简单分式"之和:\(F(x)=\sum_{m=0}^{k} \frac{A_m}{x-m}\),其中每个 \(A_m\) 是常数。
然后我们去计算 \(A_m\)。为了求 \(A_m\),我们取极限(即用 \((x-m)\) 乘以 \(F(x)\),再令 \(x\to m\))得到:\(A_m = \lim_{x\to m} (x-m)F(x) = \frac{k!}{m(m-1)\cdots1 \cdot (m-(m+1))(m-(m+2))\cdots(m-k)}\)。
我们计算两个乘积:
- 前半:\(m(m-1)\cdots 1 = m!\)
- 后半:\((m-(m+1))(m-(m+2))\cdots(m-k) = (-1)(-2)\cdots(m-k) = (-1)^{k-m}(k-m)!\)
则有:\(A_m = \frac{k!}{m! \cdot (-1)^{k-m}(k-m)!} = (-1)^{k-m}\binom{k}{m}\)。
我们发现这个系数正好就是我们求和式里那个带符号的组合数。
因此,部分分式分解就为:\(\frac{k!}{x(x-1)\cdots(x-k)} = \sum_{m=0}^{k} \frac{(-1)^{k-m}\binom{k}{m}}{x-m}\)。
接着把 \(x\) 赋值为 \(n\)。因为树有 \(n\) 个节点,且在这个推导中我们只关心 \(n>k\),所以直接代入 \(x=n\),得到:\(\frac{k!}{n(n-1)\cdots(n-k)} = \sum_{m=0}^{k} \frac{(-1)^{k-m}\binom{k}{m}}{n-m}\)。
两边同时乘以 \(n\),则:\(\sum_{m=0}^{k} (-1)^{k-m}\binom{k}{m}\frac{n}{n-m} = \frac{n \cdot k!}{n(n-1)\cdots(n-k)}\)。
右边分子分母约掉一个 \(n\):\(= \frac{k!}{(n-1)(n-2)\cdots(n-k)}\)。
最后把它写成组合数形式。我们现在得到:\(S = \frac{k!}{(n-1)(n-2)\cdots(n-k)}\)。
因为 \(\frac{n}{(n-k)} \cdot \frac{k!(n-k)!}{n!}=\frac{n}{(n-k)\binom{n}{k}}\) ,且 \((n-k)! = (n-k)(n-k-1)!\), \(n! = n(n-1)!\)。
所以原式 \(= \frac{n}{(n-k)} \cdot \frac{k! \cdot (n-k)(n-k-1)!}{n(n-1)!} = \frac{k! (n-k-1)!}{(n-1)!}\)。
而 \(\frac{k!}{(n-1)(n-2)\cdots(n-k)}\) 上下同乘 \((n-k-1)!\),得到 \(\frac{k! (n-k-1)!}{(n-1)!}\)。
两者相同,因此:\(\sum_{m=0}^{k} (-1)^{k-m}\binom{k}{m}\frac{n}{n-m} = \frac{n}{(n-k)\binom{n}{k}}\)。
所以就有 \(\mathbb{E}T=\sum_{\substack{S\subseteq V\\ S\text{ 非独立集}}}\frac{n}{(n-|S|)\binom{n}{|S|}}\)。
按大小 \(i=|S|\) 分组,大小为 \(i\) 的集合共有 \(\binom{n}{i}\) 个,其中独立集就是补集为顶点覆盖的个数为 \(f_{n-i}\)。所以非独立集数量为 \(\binom{n}{i}-f_{n-i}\)。令 \(k=n-i\),则 \(\mathbb{E}T=\sum_{i=0}^{n-1}\left(1-\frac{f_i}{\binom{n}{i}}\right)\cdot \frac{n}{n-i}\)。其中 \(f_i\) 表示大小为 \(i\) 的顶点覆盖数目。
我们考虑怎么计算 \(f_i\)。
我们对于每一个节点 \(u\),定义两个生成函数 \(A_u\) 和 \(B_u\)。其中 \(A_u(x)=\sum_{i} a_{u,i} x^i\),表示在 \(u\) 的子树内,且 \(u\) 不被选择时,顶点覆盖计数生成函数;\(B_u(x)=\sum_{i} b_{u,i} x^i\) 表示在 \(u\) 的子树内,且 \(u\) 被选时,顶点覆盖计数生成函数。
那么,当 \(u\) 为叶节点时,\(A_u(x)=1\),\(B_u(x)=x\) 因为如果不选,子树没有点,方案数为 \(1\);如果选,则要消耗 \(1\) 个顶点。
接着,当 \(u\) 为非叶子节点时,则有:
-
若不选 \(u\),则每条边 \((u,v)\) 必须由 \(v\) 覆盖,所以每个子节点 \(v\) 必须选,贡献为 \(B_v(x)\),故 \(A_u(x)=\prod_{v\in \text{son}(u)} B_v(x)\)。
-
若选 \(u\),则子节点 \(v\) 可选可不选,贡献为 \(A_v(x)+B_v(x)\),且 \(u\) 本身被选乘上 \(x\),则为 \(B_u(x)=x\cdot \prod_{v\in \text{son}(u)} \big(A_v(x)+B_v(x)\big)\)。
则整体的生成函数为:\(F(x)=A_{\text{root}}(x)+B_{\text{root}}(x)=\sum_{i=0}^{n} f_i x^i\)。
所以,我们可以用 NTT 来进行计算 \(f_i\),因为每次我们只关心当前节点和其儿子,并不关心孙子及以下的点,所以整体时间复杂度 \(O(n log^2 n)\),空间复杂度 \(O(n)\)。
下面给出代码(要卡卡常,不然会被 \(n^2\) 创飞):
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 998244353, g = 3;
struct Node {
bool operator()(const vector<int>* x, const vector<int>* y) const{
return x->size() > y->size();
}
};
int n, F[400005], G[400005], fac[100005], inv[100005];
vector<int> graph[100005], dp[100005], dp1[100005];
int ksm(int x, int y) {
int res = 1;
while (y) {
if (y & 1) {
res *= x;
res %= MOD;
}
x *= x;
x %= MOD;
y >>= 1;
}
return res;
}
int C(int x, int y) {
if (x < y || y < 0) {
return 0;
}
return fac[x] * inv[y] % MOD * inv[x - y] % MOD;
}
void NTT(int A[], int len, int op) {
for (int i = 1, j = 0; i < len; i ++) {
int pos = len >> 1;
for (; j & pos; pos >>= 1) {
j ^= pos;
}
j ^= pos;
if (i < j) {
swap(A[i], A[j]);
}
}
for (int i = 2; i <= len; i *= 2) {
int wlen = ksm(g, (MOD - 1) / i);
if (op) {
wlen = ksm(wlen, MOD - 2);
}
for (int j = 0; j < len; j += i) {
int w = 1;
for (int k = 0; k < i / 2; k ++) {
int x = A[j + k], y = w * A[j + k + i / 2] % MOD;
A[j + k] = (x + y) % MOD;
A[j + k + i / 2] = (x - y + MOD) % MOD;
w *= wlen;
w %= MOD;
}
}
}
if (op) {
int ni = ksm(len, MOD - 2);
for (int i = 0; i < len; i ++) {
A[i] *= ni;
A[i] %= MOD;
}
}
}
vector<int> work(const vector<int>& x, const vector<int>& y) {
if (x.empty() || y.empty()) {
vector<int> tmp;
return tmp;
}
int lenx = x.size(), leny = y.size(), newlen = lenx + leny - 1, len = 1;
while (len < newlen) {
len *= 2;
}
for (int i = 0; i < lenx; i ++) {
F[i] = x[i];
}
for (int i = lenx; i < len; i ++) {
F[i] = 0;
}
for (int i = 0; i < leny; i ++) {
G[i] = y[i];
}
for (int i = leny; i < len; i ++) {
G[i] = 0;
}
NTT(F, len, 0);
NTT(G, len, 0);
for (int i = 0; i < len; i ++) {
F[i] *= G[i];
F[i] %= MOD;
}
NTT(F, len, 1);
vector<int> res;
for (int i = 0; i < newlen; i ++) {
res.push_back(F[i]);
}
return res;
}
vector<int> change(vector<vector<int>>& x) {
if (x.empty()) {
return {1};
}
if (x.size() == 1) {
return move(x[0]);
}
deque<vector<int>> dq;
priority_queue<vector<int>*, vector<vector<int>*>, Node> pq;
for (auto& i : x) {
dq.push_back(move(i));
pq.push(&dq.back());
}
while (pq.size() > 1) {
vector<int>* xx = pq.top();
pq.pop();
vector<int>* yy = pq.top();
pq.pop();
dq.push_back(work(*xx, *yy));
pq.push(&dq.back());
}
return move(*pq.top());
}
void dfs(int u, int father) {
vector<vector<int>> sondp1, chF;
for (auto i : graph[u]) {
if (i == father) {
continue;
}
dfs(i, u);
vector<int> ff = dp[i];
for (int j = 0; j < dp1[i].size(); j ++) {
if (j < ff.size()) {
ff[j] = (ff[j] + dp1[i][j]) % MOD;
} else {
ff.push_back(dp1[i][j]);
}
}
chF.push_back(move(ff));
sondp1.push_back(move(dp1[i]));
}
vector<int> prod_dp1 = change(sondp1), prod_F = change(chF), b(prod_F.size() + 1, 0);
for (int i = 0; i < prod_F.size(); i ++) {
b[i + 1] = prod_F[i];
}
dp[u] = move(prod_dp1);
dp1[u] = move(b);
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
fac[0] = 1;
for (int i = 1; i <= 100000; i ++) {
fac[i] = fac[i - 1] * i % MOD;
}
inv[100000] = ksm(fac[100000], MOD - 2);
for (int i = 99999; i >= 0; i --) {
inv[i] = inv[i + 1] * (i + 1) % MOD;
}
cin >> n;
for (int i = 1; i < n; i ++) {
int x, y;
cin >> x >> y;
graph[x].push_back(y);
graph[y].push_back(x);
}
dfs(1, 1);
vector<int> f(n + 5, 0);
for (int i = 0; i <= n; i ++) {
if (i < dp[1].size()) {
f[i] += dp[1][i];
f[i] %= MOD;
}
if (i < dp1[1].size()) {
f[i] += dp1[1][i];
f[i] %= MOD;
}
}
int ans = 0;
for (int i = 0; i < n; i ++) {
ans += (1 - f[i] * ksm(C(n, i), MOD - 2) % MOD + MOD) % MOD * n % MOD * ksm(n - i, MOD - 2) % MOD;
ans %= MOD;
}
cout << ans;
return 0;
}