HDU 4219 Randomization? 树形概率DP

恩先膜拜一下岩哥.....出的题真好.....


题目大意:

就是现在有一棵有N个点的树, N <= 64, 现在每条边都随机从0~L取一个比边权值(L <= 8), 问生成的树两两结点之间距离不超过S的概率是多少(S <= 512)


大致思路:

训练赛的时候想到了用dp[u][d]表示对于结点u其子节点到它的最远距离是d的概率作为选择的状态来进行转移, 但是当时没想出来转移过程....对于u结点有多个儿子的情况不知道该怎么办

结果是个对于我来说第一次见的做法....

每次考虑儿子结点的时候一个一个的考虑, 将考虑完的部分当做一个整体和下一个儿子结点考虑进行合并, 这样每次都相当于是两个节点...感觉真是巧妙...

具体做法写在代码注释里了, 详情看代码注释吧...


代码如下:

Result  :  Accepted     Memory  :  1908 KB     Time  :  46 ms

/*
 * Author: Gatevin
 * Created Time:  2015/7/26 23:12:23
 * File Name: E.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 65

vector<int> G[maxn];
int n, L, S;

double dp[maxn][520];
double dp2[520];
double sum[2][520];
double p;
/*
 * 用dp[i][j]表示结点i到结点i所在子树中的距离的最大距离是j的概率(且其子树节点中两两距离差不超过S)
 * 初始化根节点dp[u][0] = 1, dp[u][ > 1] = 0
 * 在每次向上进行转移的时候, 例如结点u具有儿子结点v1, v2, v3... vk
 * 那么先考虑结点v1
 * 由已经得到的dp[v1][d]得到只考虑v1这个儿子的情况下dp[u][d]的所有值(O(L)枚举即可)
 * 然后考虑v2点, 由上一步得到的dp[u][d]和现在有的dp[v2][d]来推
 * 首先O(L)处理出由v2向上得到的另外一组dp2[d2], 也就是新的v2向上得到的距离为d2的概率
 * 然后枚举最长距离为maxd, 那么最长距离来自v1的话可能性是dp[u][0~maxd]*dp2[0~min(maxd, S - maxd)]
 * 最长距离来自v2的可能性是dp[u][0~min(maxd, S - maxd)]*dp2[0~maxd]
 * 对上面这两个部分求和即可, 当然注意枚举的maxd满足maxd*2 <= S的时候重复计算了dp[i][0~maxd]*dp2[0~maxd]
 * 然后考虑v3, 将之前得到的v1, v2视为一个整体和v3进行操作即可, 这就和只考虑v1和v2一样了
 * 如果还有v4...vk同样的道理考虑即可
 * 整体来说复杂度是O((n + L)*S) 一共T = 512组Case的话肯定足够了
 */


void dfs(int now, int fa)
{
    int nex;
    dp[now][0] = 1;//如果是叶子节点直接初始化0
    for(int i = 0, sz = G[now].size(); i < sz; i++) if((nex = G[now][i]) != fa)
    {
        dfs(nex, now);
        //向上回溯的时候计算
        memset(dp2, 0, sizeof(dp2));
        for(int l = 0; l <= L; l++)
            for(int s = 0; s + l <= S; s++)
                dp2[s + l] += p*dp[nex][s];
        sum[0][0] = dp[now][0];
        for(int j = 1; j <= S; j++) sum[0][j] = sum[0][j - 1] + dp[now][j];
        sum[1][0] = dp2[0];
        for(int j = 1; j <= S; j++) sum[1][j] = sum[1][j - 1] + dp2[j];
        for(int s = 0; s <= S; s++)//枚举最大长度为s
        {
            int s2 = min(s, S - s);
            double tmp = dp[now][s]*dp2[s];
            dp[now][s] = dp[now][s]*sum[1][s2] + sum[0][s2]*dp2[s];//s来自整体的这部分还是来自节点nex传上来的
            if((s << 1) <= S) dp[now][s] -= tmp;
        }
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    for(int cas = 1; cas <= T; cas++)
    {
        scanf("%d %d %d", &n, &L, &S);
        for(int i = 1; i <= n; i++) G[i].clear();
        for(int i = 1; i < n; i++)
        {
            int u, v;
            scanf("%d %d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        p = 1./(L + 1);
        memset(dp, 0, sizeof(dp));
        dfs(1, -1);
        double ans = 0;
        for(int s = 0; s <= S; s++)
            ans += dp[1][s];
        printf("Case %d: %.6f\n", cas, ans);
    }
    return 0;
}


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