[洛谷] 仓鼠找sugar II(期望DP)

Problem

题目地址

题意简述:树上任意两点 (a,b),从 (a) 随机游走到 (b) 的期望步数。(n le 10^5)

应该是一个经典题,为什么只有洛谷上能找到。

Solution

终点,注意到每个点的转移方向只有向父亲或向儿子两种,可以将期望的状态定义设计为转移向一边的期望(f[u]) 表示 (u->fa) 的期望步数,即从 (u) 向上走一步的期望步数。则有:

[f[u] = frac{1}{d_u} + frac{sum (f[v]+f[u]+1)}{d_u} ]

(其中 (d_u) 表示 (u) 的度数)

移项,则有:

[f[u] = d_u + sum f[v] ]

特别的,假设根为 (x),有 (f[x]=0)

(g(x))表示根为(x)(即终点为 (x))时的答案,(dep'[u]) 表示 (u->x) 的期望步数。根据定义:(g(x)=sum dep'[u]),其中:(dep'[u]=dep'[fa]+f[u])。进一步我们可以发现,(g(x)=sum f[u]*sz_u) 其中 (sz_u) 表示 (u) 的子树大小。

由于(x) 可以是 (1 sim n) 的任意一个点,所以我们需要算出 (g(x),1 le x le n)。于是我们可以写一个换根dp

假设 (u) 是当前的根,(v)(u) 的儿子且是下一个根。可以发现根从 (u) 换到 (v)(f[ ]) 数组中仅有 (f[u])(f[v]) 发生了改变,同样的,只有 (sz_u)(sz_v) 发生了改变。具体的

[f[u] = sum-f[v], f[v]=0 ]

(其中 (sum = sum d_u)

所以,(u) 对答案的贡献从 (0) 变成了 ((sum-f[v])(n-sz_v)),而 (v) 对答案的贡献从 (f[v]*sz_v) 变成了 (0)具体的

[g(v)=g(u)+(sum-f[v])(n-sz_v)-f[v]*sz_v ]

用两遍 (Dfs) 即可实现,时间复杂度 (O(n))

Code

Talk is cheap.Show me the code.

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 100007, mod = 998244353;
int n,cnt,sum;
int f[N],g[N],sz[N],deg[N];
int head[N];
struct Edge {
	int next,to;
}edge[N<<1];
inline void add(int u,int v) {
	edge[++cnt] = (Edge)<%head[u],v%>;
	head[u] = cnt;
}
inline void Add(int &x,int y) {
	x = (x+y>mod ? x+y-mod : x+y);
}
inline void Dec(int &x,int y) {
	x = (x-y<0 ? x-y+mod : x-y);
}
void Dfs1(int u,int fa) {
	sz[u] = 1; f[u] = deg[u];
	for(int i=head[u];i;i=edge[i].next) {
		int v = edge[i].to;
		if(v != fa) {
			Dfs1(v,u); sz[u] += sz[v]; f[u] += f[v];
		}
	}
}
void Dfs2(int u,int fa) {
	for(int i=head[u];i;i=edge[i].next) {
		int v = edge[i].to;
		if(v != fa) {
			Add(g[v], g[u]); Dec(g[v], f[v]*sz[v]%mod); Add(g[v], (sum-f[v])*(n-sz[v])%mod);
			Dfs2(v,u);
		}
	}
}
int Pow(int x,int y) {
	int res = 1, base = x;
	while(y) {
		if(y&1) res = res*base%mod; base = base*base%mod; y >>= 1;
	}
	return res;
}
signed main()
{
	n = read();
	for(int i=1;i<n;++i) {
		int u = read(), v = read();
		add(u,v), add(v,u); ++deg[u], ++deg[v];
	}
	Dfs1(1,0);
	f[1] = 0;
	for(int i=1;i<=n;++i) Add(g[1], f[i]*sz[i]%mod);
	for(int i=1;i<=n;++i) sum += deg[i];
	Dfs2(1,0);
	int ans = 0;
	for(int i=1;i<=n;++i) Add(ans, g[i]);
	ans = ans*Pow(n*n%mod, mod-2)%mod;
	printf("%lld
",ans);
	return 0;
}
/*
3
1 2
1 3

110916041
*/

Summary

经典题,没啥好说的。

  • 将期望的状态定义设计为转移向一边的期望”,具体一点,可以描述为 “(i -> i+1) 的期望步数

  • 换根dp别想半天都想不通。。。

原文地址:https://www.cnblogs.com/BaseAI/p/14001534.html