Puzzles CodeForces 696B 樹形DP 期望計算

Rewriter_huanying發表於2017-11-13

題目https://cn.vjudge.net/problem/CodeForces-696B

題意:給出一棵n個點的有根樹,節點編號從1到n,編號為1的節點為樹根。
從1號節點開始dfs,從父節點訪問子節點時對子節點的選擇是隨機的,每訪問一個節點需要1秒的時間。問訪問到每個節點的時間期望是多少秒。

思路:從樹根向下遞推。
dp(i)表示節點的時間期望,dp(fa)表示父親節點的期望,dp(child)表示父節點對應的其中一個子節點的期望。
則dp(child)的計算可分為兩種
(1)如果從父節點直接訪問到子節點
dp1(child) = dp(fa) + 1
(2)若先訪問其他子節點,再訪問該子節點,那麼先訪問其他某個子節點的所需時間為訪問完這個節點和它的所有子孫所需的時間。由於任意一個節點先於該子節點訪問的概率都為0.5,是相等的,所以其他節點的訪問時間期望貢獻具有可加性,因此
dp2(child) = 0.5 * (child的兄弟節點數 + 所有兄弟節點對應所有子孫數)

綜上:dp(child) = dp(fa) + 1 + 0.5 * (child的兄弟節點數 + 所有兄弟節點對應所有子孫數)

實現上先遞迴處理出所有節點的子孫數,然後再dfs按上式遞推即可。

程式碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;

const int maxn = 100000 + 10;

int n;

int fa[maxn];
vector<int> child[maxn];
int childsize[maxn];
double ans[maxn];

int cntchild(int u)
{
    if (child[u].empty())
    {
        return childsize[u] = 1;
    }
    childsize[u] = 1;
    for (int i = 0; i < child[u].size(); i++)
    {
        childsize[u] += cntchild(child[u][i]);
    }
    return childsize[u];
}

void dfs(int u)
{
    for (int i = 0; i < child[u].size(); i++)
    {
        int next = child[u][i];
        ans[next] = ans[u] + 1 + 0.5 * (childsize[u] - 1 - childsize[next]);
        dfs(next);
    }
}

int main()
{
    cin >> n;
    fa[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        scanf("%d", &fa[i]);
        child[fa[i]].push_back(i);
    }
    cntchild(1);
    ans[1] = 1;
    dfs(1);
    for (int i = 1; i <= n; i++)
    {
        if (i != 1)
        {
            printf(" ");
        }
        printf("%.1f", ans[i]);
    }
    return 0;
}

相關文章