BZOJ2878 [Noi2012]迷失游乐园 【基环树 + 树形dp + 期望dp】
题目链接
题解
除了实现起来比较长,思维难度还是挺小的
观察数据范围发现环长不超过\(20\),而我们去掉环上任何一个点就可以形成森林
于是乎我们枚举断掉的点,然后只需求出剩余每个点为根的答案
设\(f[i]\)表示从\(i\)出发等概率走向子树的期望步数
如果\(i\)为根就是我们所需的答案
首先求出\(f[i]\),然后用换根法扫一遍便求出每个点为根的答案
对于我们枚举的环上的点\(u\),答案自然就是
\[\sum\limits_{(u,v) \in E} \frac{f[v] + w_{(u,v)}}{de[u]}
\]
对于点\(u\)外向树中的点,再用一次换根法就可以将\(u\)的答案转移进去,然后求出的就是真实的答案了
复杂度\(O(20n)\)
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 200005,maxm = 100005,INF = 1000000000;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
int n,m,de[maxn];
int h[maxn],ne = 1;
struct EDGE{int to,nxt; double w;}ed[maxn << 1];
inline void build(int u,int v,double w){
ed[++ne] = (EDGE){v,h[u],w}; h[u] = ne;
ed[++ne] = (EDGE){u,h[v],w}; h[v] = ne;
de[u]++; de[v]++;
}
double f[maxn],up[maxn],ans[maxn];
int fa[maxn],Out[maxn],rt;
int c[maxn],ci,vis[maxn],inc[maxn],at[maxn];
void dfs1(int u){
f[u] = 0; vis[u] = true;
if (u != rt && de[u] == 1) return;
if (!de[u]) return;
double d = (u == rt) ? 1.0 / de[u] : 1.0 / (de[u] - 1);
Redge(u) if ((to = ed[k].to) != fa[u] && !Out[to]){
fa[to] = u; up[to] = ed[k].w; dfs1(to);
f[u] += (f[to] + ed[k].w) * d;
}
}
void dfs2(int u){
if (u != rt){
int v = fa[u];
if (de[v] == 1) f[u] = f[u] * (de[u] - 1) / de[u] + up[u] / de[u];
else {
double tmp = f[v] * de[v] / (de[v] - 1) - (f[u] + up[u]) / (de[v] - 1);
f[u] = f[u] * (de[u] - 1) / de[u] + (tmp + up[u]) / de[u];
}
}
Redge(u) if ((to = ed[k].to) != fa[u] && !Out[to])
dfs2(to);
}
void solve1(){
rt = 1;
dfs1(1);
dfs2(1);
double ans = 0;
REP(i,n) ans += f[i];
printf("%.5lf\n",ans / n);
}
void dfs3(int u){
vis[u] = true;
Redge(u) if ((to = ed[k].to) != fa[u]){
if (vis[to]){
if (ci) continue;
for (int i = u; i != to; i = fa[i]) c[++ci] = i,inc[i] = true;
c[++ci] = to; inc[to] = true;
}
else fa[to] = u,dfs3(to);
}
}
void dfs4(int u){
f[u] = 0;
if (de[u] == 1) return;
double d = 1.0 / (de[u] - 1);
Redge(u) if ((to = ed[k].to) != fa[u] && !Out[to]){
fa[to] = u; up[to] = ed[k].w; dfs4(to);
f[u] += (f[to] + ed[k].w) * d;
}
}
void dfs5(int u){
int v = fa[u];
if (de[v] == 1) f[u] = f[u] * (de[u] - 1) / de[u] + up[u] / de[u];
else {
double tmp = f[v] * de[v] / (de[v] - 1) - (f[u] + up[u]) / (de[v] - 1);
f[u] = f[u] * (de[u] - 1) / de[u] + (tmp + up[u]) / de[u];
}
ans[u] = f[u];
Redge(u) if ((to = ed[k].to) != fa[u])
dfs5(to);
}
void work(int u){
Out[u] = true;
REP(i,n) vis[i] = false,at[i] = false; vis[u] = true;
Redge(u){
de[to = ed[k].to]--;
if (!inc[to]) at[to] = true;
}
Redge(u) if (!vis[to = ed[k].to]){
fa[rt = to] = 0;
dfs1(rt);
dfs2(rt);
}
double d = 1.0 / de[u];
Redge(u) ans[u] += (f[to = ed[k].to] + ed[k].w) * d;
f[u] = ans[u];
Redge(u){
de[to = ed[k].to]++;
if (!inc[to]){
rt = to; fa[to] = u; up[to] = ed[k].w;
dfs4(rt);
dfs5(rt);
}
}
Out[u] = false;
}
void solve2(){
dfs3(1);
REP(i,ci) work(c[i]);
double re = 0;
REP(i,n) re += ans[i];
re /= n;
printf("%.5lf\n",re);
}
int main(){
n = read(); m = read(); int a,b,w;
REP(i,m){
a = read(); b = read(); w = read();
build(a,b,w);
}
if (m == n - 1) solve1();
else solve2();
return 0;
}