[bzoj4899]记忆的轮廓 题解(毒瘤概率dp)

题目背景

四次死亡轮回后,昴终于到达了贤者之塔,当代贤者夏乌拉一见到昴就上前抱住了昴“师傅!你终于回来了!你有着和师傅一样的魔女的余香,肯定是师傅”。
众所周知,大贤者是嫉妒魔女沙提拉的老公,400年前与神龙、剑圣一起封印魔女因子暴走的莎缇拉。在魔女茶会的时候,莎缇拉也表示过对昴浓浓的爱意,昴便是被莎缇拉召唤来异世界的。
而贤者之塔中的资料与试炼,似乎都指向同一种可能性……记忆的轮廓,逐渐显形……

题目描述

通往贤者之塔的路上,有许多的危机。
我们可以把这个地形看做是一颗树,根节点编号为1,目标节点编号为n,其中1-n的简单路径上,编号依次递增,在[1,n]中,一共有n个节点。
我们把编号在[1,n]的叫做正确节点,[n+1,m]的叫做错误节点。一个叶子,如果是正确节点则为正确叶子,否则称为错误叶子。
莎缇拉要帮助昴到达贤者之塔,因此现在面临着存档位置设定的问题。为了让昴成长为英雄,因此一共只有p次存档的机会,其中1和n必须存档。被莎缇拉设置为要存档的节点称为存档位置。
当然不能让昴陷入死循环,所以存档只能在正确节点上进行,而且同一个节点不能存多次档。因为通往贤者之塔的路上有影响的瘴气,因此莎缇拉假设昴每次位于树上一个节点时,都会等概率选择一个儿子走下去。每当走到一个错误叶子时,再走一步就会读档。
具体的,每次昴到达一个新的存档位置,存档点便会更新为这个位置(假如现在的存档点是i,现在走到了一个存档位置j>i,那么存档点便会更新为j)。读档的意思就是回到当前存档点。
初始昴位于1,当昴走到正确叶子n时,便结束了路程。莎缇拉想知道,最优情况下,昴结束路程的期望步数是多少?
输入格式

第一行一个正整数T表示数据组数。
接下来每组数据,首先读入三个正整数n,m,p。
接下来m-n行,描述树上所有的非正确边(正确边即连接两个正确节点的边),用两个正整数j,k表示j与k之间有一条连边,j和k可以均为错误节点,也可以一个为正确节点另一个为错误节点。数据保证j是k的父亲。
输出格式

T行每行一个实数表示每组数据的答案。请保留四位小数。

样例输入


1
3 7 2
1 4
2 5
3 6
3 7

样例输出

9.000

数据范围及约定

50%,n=p
70%,50<=p<=n<=500
100%,50<=p<=n<=700,m<=1500,T<=5
数据保证每个除了n的正确节点均有至少2个儿子,至多3个儿子。

戳这里膜拜出题人

要是考试的时候题面上有50%n=p就先写这个了

首先考虑n=p的做法:

设g[i]表示对于一个错误节点i,期望走多少步会读档

则$g[i]=frac{1}{du[i]cdot{sum_{(j为i的儿子  )}  {  g[j]}}}+1$

另外预处理

$sum[i]=sum{g[j]}$,j为i的错误儿子

那么对于正确节点i走到n的期望步数f[i],有

$f[i]=du[i]+f[i+1]+sum[i]$

之后是对于普遍情况,比较暴力的转移做法

设f[i,j]表示当前存档点为i,还剩j次存档机会。

我们需要预处理一个step[i,j],表示存档点为i,从i开始走到正确节点j的期望步数(中间不能存档)

那么显然有

$step[i][i]=0$

$step[i][j]=step[i][j-1]*du[j-1]+du[j-1]+sum[j-1] (i<j)$

预处理step的时间为n2

之后枚举存档点转移即可

复杂度O(n2p)

最后的正解

被博主鸽了hhh

“咦但是你A了这道题啊?”

呃是这样的

出题人最终给了三种正解

博主选择的是第二种

但是并不会证明它的正确性(因为真的很像qj测试点?)

其实就是设定一个距离,只在距离内转移dp数组

事实上,我觉得出题人在解释这种做法时给出的解释也很含糊

好像只是说了为什么step数组爆炸增长不会影响大局,没有给出指定距离内转移的合理性

所以大家还是直接点上面的链接看出题人讲解好了

博主有时间的话也会尝试使用另外两种做法A掉这道题的

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9')
    {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')
    {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
const int N=2005,dis=15;
int n,m,p,T;
int du[N],nxt[N],to[N],head[N],tot=0;
double g[N],dp[N][N],step[N][N],sum[N];
bool v[N];
void add(int x,int y)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
    du[x]++;
}
void dfs(int x)
{
    v[x]=1;g[x]=1;
    for(int i=head[x];i;i=nxt[i])
    {
        dfs(to[i]);
        g[x]+=(double)1/du[x]*g[to[i]];
    }
}
void work()
{
    n=read();m=read();p=read();
    for(int i=1;i<=m;i++)
    head[i]=to[i]=nxt[i]=du[i]=v[i]=sum[i]=0;
    tot=0;
    for(int i=1;i<=m-n;i++)
    {
        int x=read(),y=read();
        add(x,y);
    }
    for(int i=1;i<n;i++)du[i]++;
    for(int i=n+1;i<=m;i++)
        if(!v[i])dfs(i);
    for(int i=1;i<=n;i++)
        for(int j=head[i];j;j=nxt[j])
            sum[i]+=g[to[j]];
    //for(int i=1;i<=n;i++)printf("%lf
",sum[i]);
    for(int i=1;i<=n;i++)
    {
        step[i][i]=0;
        for(int j=i+1;j<=n;j++)
            step[i][j]=(double)du[j-1]*step[i][j-1]+sum[j-1]+du[j-1];
    }
    memset(dp,127,sizeof(dp));
    dp[n][1]=0;
    for(int j=2;j<=p;j++)
        for(int i=1;i<=n;i++)
            for(int k=i+1;k<=n&&k-i<=dis;k++)
                dp[i][j]=min(dp[i][j],dp[k][j-1]+step[i][k]);
    printf("%.4lf
",dp[1][p]);
}
int main()
{
    T=read();
    while(T--)work();
    return 0;
}
原文地址:https://www.cnblogs.com/Rorschach-XR/p/11044232.html