HDU 4443 Lost 树形概率DP

题目大意:

就是现在给出一个有且仅有一个环的图, 顶点数<= 10^5, 环的大小在3~30之间, 现在初始的时候随机选一个点作为起点, 然后每次选择与当前所在的点相邻的没有走过的点进入, 直到不能走为止, 求最终停在各个点的概率, 输出其中最大的5个的和


大致思路:

很容易考虑到将环拆开成之多30个在树上做概率DP, 不过细节真的很多, 很容易出错, 细节见代码注释


代码如下:

Result  :  Accepted     Memory  :  8660 KB     Time  :  280 ms

/*
 * Author: Gatevin
 * Created Time:  2015/3/16 18:29:36
 * File Name: Kotori_Itsuka.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

#define maxn 100010
int n, pre[maxn], nxt[maxn];
vector <int> G[maxn];
vector <int> circle;
bool vis[maxn], inCircle[maxn];
double up[maxn], down[maxn], ans[maxn], p;

bool findCircle(int now, int father)//dfs寻找环, father表示上一个经过的点
{
    for(int i = G[now].size() - 1; i >= 0; i--)
    {
        int nex = G[now][i];
        if(nex == father) continue;
        if(!vis[nex])
        {
            vis[nex] = 1;
            pre[nex] = now;
            nxt[now] = nex;
            if(findCircle(nex, now))
                return true;
            vis[nex] = 0;
        }
        else
        {
            pre[nex] = now;
            nxt[now] = nex;//pre和nxt数组分别两个方向记录环的节点的相邻关系
            circle.push_back(nex);//环上的点存储在circle里
            inCircle[nex] = 1;
            while(now != nex)
                circle.push_back(now), inCircle[now] = 1, now = pre[now];
            return true;
        }
    }
    return false;
}

/*
 * Up函数计算某棵环上的子树中的点从下向上到达点i的概率up[i]
 * 从下向上是从叶子节点向根节点(环上的点)的方向
 * 从下向上时根节点往叶节点的方向
 */
void Up(int now, int father)
{
    for(int i = G[now].size() - 1; i >= 0; i--)
    {
        int nex = G[now][i];
        if(nex == father || inCircle[nex]) continue;
        Up(nex, now);
        if(G[nex].size() > 1)//不是叶子节点
            up[now] += up[nex]/(G[nex].size() - 1);//nex作为过渡
        up[now] += p/G[nex].size();//nex被直接选中
    }
    return;
}

/*
 * 从其他子树出发到这棵子树的点作为终点
 */
void Down1(int now, int father)//计算从其它子树节点出发往这棵子树下走到尽头的概率
{
    int cnt = 0;
    if(inCircle[now])//起点(往树下走的概率在之前已经算了)
        cnt = 1;//cnt = 1, 不影响到向下走的概率
    else
        for(int i = G[now].size() - 1; i >= 0; i--)
            if(!inCircle[G[now][i]] && G[now][i] != father)
                cnt++;//可以走的点数
    for(int i = G[now].size() - 1; i >= 0; i--)
    {
        int nex = G[now][i];
        if(nex == father || inCircle[nex]) continue;
        down[nex] += down[now]/cnt;//这里不能 + p/G[now].size()了(这个情形分类下now不能作为起点)
        Down1(nex, now);
    }
    if(!inCircle[now] && G[now].size() == 1)//叶子节点
        ans[now] += down[now];
    return;
}

/*
 * 从自己这棵子树的点(包括自己的根节点)出发在自己这棵树走到终点的情况
 */
void Down2(int now, int father)
{
    int cnt = 0;
    for(int i = G[now].size() - 1; i >= 0; i--)
        if(!inCircle[G[now][i]] && G[now][i] != father)
            cnt++;
    for(int i = G[now].size() - 1; i >= 0; i--)
    {
        int nex = G[now][i];
        if(nex == father || inCircle[nex]) continue;
        down[nex] += p/G[now].size();//now作为起点
        if(!inCircle[now])//now不是根节点就可以是路径上的点
            down[nex] += down[now]/(G[now].size() - 1);//从上向下的路径
        //接下来是从下往上到达now之后再往下到达nex的概率
        double tmp = up[now] - p/G[nex].size();//从up[now]中减去来自nex的部分
        if(G[nex].size() > 1) tmp -= up[nex]/(G[nex].size() - 1);
        down[nex] += tmp/(G[now].size() - 1);
        Down2(nex, now);
    }
    if(!cnt && !inCircle[now])
        ans[now] += down[now];
    return;
}

void init()
{
    for(int i = 1; i <= n; i++) G[i].clear();
    circle.clear();
    p = 1./n;//p是某个节点被选中作为初始的概率
    int tx, ty;
    for(int i = 1; i <= n; i++)
    {
        scanf("%d %d", &tx, &ty);
        G[tx].push_back(ty);
        G[ty].push_back(tx);
    }
    memset(vis, 0, sizeof(vis));
    memset(inCircle, 0, sizeof(inCircle));
    memset(up, 0, sizeof(up));
    memset(down, 0, sizeof(down));
    memset(ans, 0, sizeof(ans));
    return;
}

int main()
{
    while(scanf("%d", &n), n)
    {
        init();
        vis[1] = 1;
        findCircle(1, -1);
        for(int i = circle.size() - 1; i >= 0; i--)
        {
            int now = circle[i];
            Up(now, -1);
            /* 
             * 接下来计算对于环上的每个点作为过渡点往下前进的转折点的概率
             * 也就是每棵子树上的点不作为起点但是这棵子树上的点作为终点时到达其根节点的概率
             * 以及换上的可以作为终点的点的概率
             */
            double tmp = up[now]/(G[now].size() - 1) + p/G[now].size();
            while(nxt[now] != circle[i])
            {
                if(nxt[nxt[now]] != circle[i])
                    down[nxt[now]] += tmp/(G[nxt[now]].size() - 1);//往下走
                else if(G[nxt[now]].size() != 2)
                    down[nxt[now]] += tmp/(G[nxt[now]].size() - 2);//环的尽头往下走
                else ans[nxt[now]] += tmp;
                /*
                 * 当circle[i]是上来的起点时只有左或者右边的点的度数是2的时候
                 * 转一圈那个点才会是终点
                 */
                now = nxt[now];
                tmp = tmp/(G[now].size() - 1);
            }
            now = circle[i];
            tmp = up[now]/(G[now].size() - 1) + p/G[now].size();
            while(pre[now] != circle[i])
            {
                if(pre[pre[now]] != circle[i])
                    down[pre[now]] += tmp/(G[pre[now]].size() - 1);
                else if(G[pre[now]].size() != 2)
                    down[pre[now]] += tmp/(G[pre[now]].size() - 2);
                else ans[pre[now]] += tmp;
                now = pre[now];
                tmp = tmp/(G[now].size() - 1);
            }
        }
        for(int i = circle.size() - 1; i >= 0; i--)
            Down1(circle[i], -1);
        memset(down, 0, sizeof(down));
        for(int i = circle.size() - 1; i >= 0; i--)
            Down2(circle[i], -1);
        sort(ans + 1, ans + n + 1);
        double result = 0;
        for(int i = 0; i < 5; i++)
            result += ans[n - i];
        printf("%.5f\n", result);
    }
    return 0;
}


你可能感兴趣的:(HDU,lost,树形DP,概率DP,4443)