[BZOJ2878][Noi2012]迷失游乐园 && 树形DP

11 篇文章 0 订阅
5 篇文章 0 订阅


因为该图要么是一棵书 要么是一个环 所以我们分开处理 从较为简单的树入手


part1:题目转化为 求一棵树上从任何一个节点出发的最长路径的期望值

      显然我们可以发现全局期望值等于每个点的期望值d[i]的和除以点的数量 所以我们需要处理出每个点出发的最长路径的期望值

     对于任何一个点来说 他有两种行走的方向 一种是向父亲行走 一种是向儿子行走 我们假设d[i] 表示i出发向所有方向的路径期望总和, 用du保存每个节点在树中的邻接点的数量(注意 是在树中而不是在图中 这个定义将对处理环时产生影响)

     首先我们需要处理向儿子行走的期望值 用f[i]表示从i出发向以i为根的子树行走的期望值则有 f[i] = sigema ( f[j] + w(i, j) ) / (d[u] - 1) = d[i] / (du[i] - 1) ; (注意 此时d[i]仅包含向子树行走的路径和) 

     然后我们需要处理向父亲行走的期望值 由于每个节点的f[i]已经处理出来了 对于我们之后的计算方便了很多 对于每一个点i来说 d[i] += (d[fa] - f[i] - w(fa(i), i)) / (du[fa]-1) + wt;

     得到这些信息后我们就可以处理树的问题了

part2:剩下我们就需要处理环图的问题了

      首先我们可以发现环上的点是难以处理的 我们只能先处理环上点的子树 那么我们就需要把环上的点先找出来并标记好

      然后像处理树一样处理每一个环上节点为根的子树

      问题在于处理环上点的关系 我们发现 环上的点最多只有20个 这告诉我们可以选择一种相对暴力的方法来处理环的问题

      对于每个环上的点 在环上进行dp 我们定义g[i]为第一条边沿着环边走的期望值 则有

g[i] = sigema(g[j] + w(i, j))  / (du[i] + 1)

      最后统计答案即可 需要注意的是统计答案时每一个环上节点i的邻接点数其实是du[i]+2, 计算期望时需要注意

       至此 本题得到解决


       

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
#define SF scanf  
#define PF printf
using namespace std;
typedef long long LL;
typedef long double LD;
const int MAXN = 100000;
int n, m, du[MAXN+10], rt;
int vis[MAXN+10], fa[MAXN+10];
bool cir[MAXN+10];
// Graphs
struct Node {
    int v, wt, next;
} Edge[MAXN*2+10];
int adj[MAXN+10], ncnt;
void addedge(int u, int v, int wt) {
    Node &e = Edge[++ncnt];
    e.v = v; e.wt = wt;
    e.next = adj[u]; adj[u] = ncnt;
}
void add(int u, int v, int wt) { addedge(u, v, wt); addedge(v, u, wt); }
 
// Get Trees' E
LD d[MAXN+10], f[MAXN+10]; // f向下 d总
void Tree_dp(int u) {
    d[u] = f[u] = 0;
    vis[u] = 1; du[u] = 0;
    for(int i = adj[u]; ~i; i = Edge[i].next) {
        int v = Edge[i].v, wt = Edge[i].wt;
        if(vis[v] || cir[v]) continue;
        Tree_dp(v);
        du[u]++; d[u] += f[v] + wt;
    }
    if(du[u]) f[u] = d[u] / du[u];
    du[u] += u != rt;
}
 
// DP father's E
void Fa_dp(int u) {
    vis[u] = 1;
    for(int i = adj[u]; ~i; i = Edge[i].next) {
        int v = Edge[i].v, wt = Edge[i].wt;
        if(vis[v] || cir[v]) continue ;
        int tot = du[u] - 1;
        if(tot == 0) tot++; 
        d[v] += (LD) (d[u] - f[v] - wt) / (LD) tot + wt;
        Fa_dp(v);
    }
}
 
// Find circles
bool Find(int u, int pre) {
    vis[u] = 1;
    for(int i = adj[u]; ~i; i = Edge[i].next) {
        int v = Edge[i].v, wt = Edge[i].wt;
        if(!vis[v]) {
            fa[v] = u;
            if(Find(v, u)) return true;
        }
        else if(v != fa[u]) {
            int x = u;
            cir[v] = true;
            while(x != v) {
                cir[x] = true;
                x = fa[x];
            }
            return true;
        }
    }
    return true;
}
 
// dp on the circle
LD g[MAXN+10];
int st;
void dp(int u, int pre) {
    bool end = true;
    g[u] = 0;
    for(int i = adj[u]; ~i; i = Edge[i].next) {
        int v = Edge[i].v, wt = Edge[i].wt;
        if(v == st || v == pre || !cir[v]) continue ;
        end = false;
        dp(v, u);
        g[u] += g[v] + wt;
    }
    int tot = du[u];
    if(!tot) tot++;
     
    if(end) g[u] = (LD) d[u] / tot;
    else {
        tot = du[u] + 1;
        if(u != st) g[u] = (LD) (g[u] + d[u]) / tot;
        else return ;
    }
}
 
// Main()
LD Cird[MAXN+10];
int main()
{
    memset(adj, -1, sizeof(adj));
    double ans = 0;
    SF("%d%d", &n, &m);
    for(int i = 1; i <= m; i++) {
        int u, v, wt;
        SF("%d%d%d", &u, &v, &wt);
        add(u, v, wt);
    }
    if(n == m+1) {
        rt = 1;
        Tree_dp(1);
        memset(vis, 0, sizeof(vis));
        Fa_dp(1);
    }
    else {
        Find(1, 0);
        memset(vis, 0, sizeof(vis));
        for(int i = 1; i <= n; i++)
            if(cir[i])
                rt = i, Tree_dp(i);
        for(int i = 1; i <= n; i++)
            if(cir[i]) {
                st = i;
                dp(i, 0);
                Cird[i] = g[i];
            }
        for(int i = 1; i <= n; i++) 
            if(cir[i])
                du[i] += 2, d[i] += Cird[i];
        memset(vis, 0, sizeof(vis));
        for(int i = 1; i <= n; i++)
            if(cir[i])
                Fa_dp(i);
    }
    for(int i = 1; i <= n; i++)
        ans = ans + d[i] / du[i];
    PF("%.5f", ans / n);
}
/*
4 4
 
1 2 3 
 
2 3 1 
 
3 4 4
 
4 1 5
*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值