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

版权声明:我这种蒟蒻的文章,真正的大佬一般看不上的_(:з」∠)_ https://blog.csdn.net/Paulliant/article/details/82669208

题意

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

思路

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

a d 数组表示新添加的子树 v d p t m p d p u 的一个滚动(先把新的值整体赋回去)。
观察上述代码,发现用添加子树两层循环时间开销过大,考虑通过前缀和优化掉一层。可以先令 j k ,然后乘上所有的 k [ 0 , m i n { j , S j } ] 的和,同理令 k 大,乘上所有 j [ 0 , m i n { k , S 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;
}

猜你喜欢

转载自blog.csdn.net/Paulliant/article/details/82669208