HDU-4219 Randomization?(树形DP+概率DP)

题意

给定一棵 n n 个节点的树,每条边的权值为 [0,L] 之间的随机整数,求这棵树两点之间最长距离不超过 S S 的概率。
1n1000
1L10 1 ≤ L ≤ 10
1S2000 1 ≤ S ≤ 2000

思路

这种概率题以前没碰到过,现在碰到连暴力也不会打。其实样本点除以样本空间是最稳的暴力。从 1 1 2n 均有一条边的特殊情况入手,比较显然需要一条边一条边添加。用 dpi d p i 保存最大边权为 i i 的合法情况的概率(也就是说不能存在大于 Si 的另一条边)。而树的情况,也不就是变成了一个子树一个子树添加, dfs d f s 如下:

void dfs(int u,int f)
{
    dp[u][0]=1;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f)continue;
        dfs(v,u);
        memset(ad,0,sizeof(ad));
        memset(tmp,0,sizeof(tmp));
        FOR(j,0,L)
            FOR(k,0,S-j)
                ad[k+j]+=dp[v][k]/(L+1);
        FOR(j,0,S)
            FOR(k,0,S-j)
                tmp[max(j,k)]+=dp[u][j]*ad[k];
        FOR(j,0,S)dp[u][j]=tmp[j];
    }
}

ad a d 数组表示新添加的子树 v v dp tmp t m p dpu d p u 的一个滚动(先把新的值整体赋回去)。
观察上述代码,发现用添加子树两层循环时间开销过大,考虑通过前缀和优化掉一层。可以先令 jk j ≥ k ,然后乘上所有的 k[0,min{j,Sj}] k ∈ [ 0 , m i n { j , S − j } ] 的和,同理令 k k 大,乘上所有 j[0,min{k,Sk}] ,最后发现 j=k j = k 的情况被算了两次,减掉一次即可。

代码

#include<bits/stdc++.h>
#define FOR(i,x,y) for(register int i=(x);i<=(y);++i)
#define DOR(i,x,y) for(register int i=(x);i>=(y);--i)
#define N 1003
typedef long long LL;
using namespace std;
template<const int maxn,const int maxm>struct Linked_list
{
    int head[maxn],to[maxm],nxt[maxm],tot;
    void clear(){memset(head,-1,sizeof(head));tot=0;}
    void add(int u,int v){to[++tot]=v,nxt[tot]=head[u],head[u]=tot;}
    #define EOR(i,G,u) for(register int i=G.head[u];~i;i=G.nxt[i])
};
Linked_list<N,N<<1>G;
double dp[N][2003],ad[2003],sumdp[2003],sumad[2003],tmp[2003];
int n,L,S;
void dfs(int u,int f)
{
    dp[u][0]=1;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f)continue;
        dfs(v,u);
        memset(tmp,0,sizeof(tmp));
        memset(ad,0,sizeof(ad));
        FOR(j,0,L)
            FOR(k,0,S-j)
                ad[k+j]+=dp[v][k]/(L+1);
        sumad[0]=ad[0];
        sumdp[0]=dp[u][0];
        FOR(j,1,S)sumad[j]=sumad[j-1]+ad[j],sumdp[j]=sumdp[j-1]+dp[u][j];
        FOR(j,0,S)
        {
            int k=min(j,S-j);
            tmp[j]+=dp[u][j]*sumad[k];
        }
        FOR(k,0,S)
        {
            int j=min(k,S-k);
            tmp[k]+=ad[k]*sumdp[j];
        }
        FOR(j,0,S/2)tmp[j]-=dp[u][j]*ad[j];
        FOR(j,0,S)dp[u][j]=tmp[j];
    }
}

int main()
{
    G.clear();
    scanf("%d%d%d",&n,&L,&S);
    FOR(i,1,n-1)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G.add(u,v);
        G.add(v,u);
    }
    dfs(1,0);
    double ans=0;
    FOR(i,0,S)ans+=dp[1][i];
    printf("%.6lf\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值