bzoj2878: [Noi2012]迷失游乐园 基环树+Dp

bzoj2878: [Noi2012]迷失游乐园

Description

放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩。进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点、m条道路的无向连通图,且该图中至多有一个环(即m只可能等于n或者n-1)。小Z现在所在的大门也正好是一个景点。小Z不知道什么好玩,于是他决定,从当前位置出发,每次随机去一个和当前景点有道路相连的景点,并且同一个景点不去两次(包括起始景点)。贪玩的小Z会一直游玩,直到当前景点的相邻景点都已经访问过为止。小Z所有经过的景点按顺序构成一条非重复路径,他想知道这条路径的期望长度是多少?小Z把游乐园的抽象地图画下来带回了家,可是忘了标哪个点是大门,他只好假设每个景点都可能是大门(即每个景点作为起始点的概率是一样的)。同时,他每次在选择下一个景点时会等概率地随机选择一个还没去过的相邻景点。

Input

第一行是两个整数n和m,分别表示景点数和道路数。 接下来行,每行三个整数Xi, Yi, Wi,分别表示第i条路径的两个景点为Xi, Yi,路径长Wi。所有景点的编号从1至n,两个景点之间至多只有一条道路。

Output

共一行,包含一个实数,即路径的期望长度,保留五位小数

Sample Input

4 3
1 2 3
2 3 1
3 4 4

Sample Output

6.00000
【样例解释】样例数据中共有6条不同的路径: 路径 长度 概率
1–>4 8 1/4
2–>1 3 1/8
2–>4 5 1/8
3–>1 4 1/8
3–>4 4 1/8
4–>1 8 1/4
因此期望长度 = 8/4 + 3/8 + 5/8 + 4/8 + 4/8 + 8/4 = 6.00
【评分方法】本题没有部分分,你程序的输出只有和标准答案的差距不超过0.01时,才能获得该测试点的满分,否则不得分。
【数据规模和约定】对于100%的数据,1 <= Wi <= 100。 测试点编号 n m 备注
1 n=10 m = n-1 保证图是链状
2 n=100 只有节点1的度数大于2
3 n=1000 /
4 n=100000 /
5 n=100000 /
6 n=10 m = n /
7 n=100 环中节点个数<=5
8 n=1000 环中节点个数<=10
9 n=100000 环中节点个数<=15
10 n=100000 环中节点个数<=20

分析

比较难的一道概率Dp
首先考虑树的做法。
之前有一道叫概率充电器的题目可以了解一下。
处理这类树上随机走的期望一般需要上下Dp两次。
具体地,处理只向下走的期望和只向上走的期望,然后承上概率相加就是答案。
向下 g u = ( g v + w u , v ) s o n u
v u 的儿子, s o n u u 的儿子个数
向上 h u = w u , f + ( g f s o n f g u w u , f ) + h f d u 1
d u u 的度数,意思就是先向上走一步,然后可以往除了当前子树的其他任意一个方向走一步。
最后的答案就是 a n s n = h u + g u s u d u
树上的情况处理完了,现在考虑环。
对于每棵外向树,不难发现向上向下走的期望的处理方法是和树上的处理方法一模一样的,唯一的改变是根节点向上走的期望不再是零,而是它绕环顺时针逆时针走的期望。
但是向下走是不影响的。于是我们先处理向下走。
然后考虑环,顺时针逆时针也是一样的。
f u = f v + w u , v + g v s v d v 1
其中 v u 在环某个方向上的相邻节点。
注意环首尾要特判。
注意这个Dp是基于从环上某一个节点出发的,所以需要每个节点都做一遍,复杂度是 O ( k 2 ) , k 是环上节点的个数
处理完之后把顺时针和逆时针 f 的值当作环上节点向上走的期望,再做一遍和树上Dp一模一样的向上Dp
然后就写完了。
蛮有意思的一道期望题。

代码

#include<cstdio>
const int N = 2e5 + 10;
int ri() {
    char ch = getchar(); int x = 0;
    for(;ch < '0' || ch > '9'; ch = getchar()) ;
    for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) - '0' + ch;
    return x;
}
int to[N], nx[N], pr[N], w[N], s[N], d[N], in[N], f[N], st[N], tot, tp, n, x;
double g[N], h[N];
bool c[N];
void add(int u, int v, int ww) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp; w[tp] = ww;}
void adds(int u, int v, int w) {add(u, v, w); add(v, u, w); ++d[u]; ++d[v];}
double Down(int u, int f) {
    s[u] = 0;
    for(int i = pr[u]; i; i = nx[i]) 
        if(to[i] != f && !c[to[i]]) g[u] += Down(to[i], u) + w[i], ++s[u];
    return g[u] = (s[u] ? g[u] / s[u] : 0);
}
void Up(int u, int f) {
    for(int i = pr[u]; i; i = nx[i]) if(to[i] != f && !c[to[i]]) {
        if(d[u] == 1) h[to[i]] = w[i];
        else h[to[i]] = w[i] + (h[u] + g[u] * s[u] - g[to[i]] - w[i]) / (d[u] - 1);
        Up(to[i], u);
    }
}
void Fc(int u) {
    in[u] = ++tot;
    for(int i = pr[u], v; i; i = nx[i]) 
        if((v = to[i]) != f[u]) {
            if(in[v]) {
                if(in[v] < in[u]) continue; c[st[++tp] = u] = true;
                for(;v != u; v = f[v]) c[st[++tp] = v] = true;
            }
            else f[v] = u, Fc(v);
        }
}
double Calc(int u, int f) {
    double v = 0; int cnt = 0;
    for(int i = pr[u]; i; i = nx[i]) 
        if(to[i] != f && to[i] != x && c[to[i]]) v = Calc(to[i], u) + w[i], ++cnt;
    return cnt ? (v + g[u] * s[u]) / (d[u] - 1) : g[u];
}
int main() {
    n = ri(); int m = ri();
    for(int i = 1, u, v;i <= m; ++i) u = ri(), v = ri(), adds(u, v, ri()); tp = 0;
    if(m == n - 1) Down(1, 0), Up(1, 0);
    else {
        Fc(1); for(int i = 1;i <= tp; ++i) Down(st[i], 0);
        for(int j = 1;j <= tp; ++j) {
            x = st[j];
            for(int i = pr[st[j]]; i; i = nx[i])
                if(c[to[i]]) h[st[j]] += w[i] + Calc(to[i], st[j]);
            Up(st[j], 0);
        }
    }
    double ans = 0;
    for(int i = 1;i <= n; ++i) ans += (h[i] + g[i] * s[i]) / d[i];
    printf("%.5lf\n", ans / n);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lvzelong2014/article/details/80647728