4850. 【GDOI2017模拟11.3】记忆的轮廓

这是一篇关于如何利用记忆化搜索解决概率期望问题的博客。文章通过一道题目介绍了如何在存在概率和期望的情况下,分析问题并找到解题思路。在n=p的情况下,可以通过动态规划求解每个节点的期望步数。对于一般情况,利用单调队列或设定阈值优化复杂度,以求得最优解。
摘要由CSDN通过智能技术生成

题目描述

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

分析

对付概率、期望的题目,经常会出现需要解方程的时候。
定义边界:由于题目限制,到此状态的时候有准确的期望值。
这种时候有两种情况:1,有边界,这种就可以通过dp或记忆化搜索逐步解单一方程,求出一些期望;2,无边界,可能需要高斯消元解方程,求出所有解。
回看这道题,看似无法分析,一看就蒙。很多期望题都是这样,这个时候我们要找切入点。
先考虑n=p时的情况,这个时候,从1到n的简单路径都设为存档点,那么我们只用知道,对于这条简单路径上的一个节点i,它往下走的期望是多少。我们知道在这个时候,一旦走到错误节点,那肯定回到自己。设i往下走的期望步数f[i],走到错误叶子的期望步数g[i],有tot[i]个儿子,那么
这里写图片描述
这样求出f[i]后就很轻易地弄出n=p时的答案了嘛。
那么一般情况呢?
设fx[i][j]为第i个点走到最终点(我们把简单路径编号),设置了j个存档点的期望步数,那么对于一个fx[i][j]就可以转移到fx[1~i-1][j+1]嘛。为了好做,我们设a[i][j]表示i设存档点,走到j的期望步数。然后再列出一个方程,解之即求出a。
考虑此时时间复杂度:3次方,不行。
此时观察a的方程后,发现增量的单调性,可以用单调队列进行优化,降一级复杂度。
另一个较好的思路是:我们发现a[i]增长十分恐怖,可以设定阈值,爆了就不算下去了,因为那样太蠢了,想象一下前面一大段没有存档点,最后存档点扎堆····
我们可以均匀地放存档点,把此时的期望步数设为阈值,这样便可以放心DP了,事实证明,这样很快。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
const int N=2005;//记得改回来!!!!!!!!!!!!! 
int b[N],next[N],first[N],first1[N],next1[N],t1,tt;
int T,i,j,n,m,x,y,p;
double sum[N],b1[N],c1[N],res,xs,f[N];
void cr(int x,int y)
{
    tt++;
    b[tt]=y;
    next[tt]=first[x];
    first[x]=tt;
    sum[x]+=1;
}
void cr1(int x,int y,double z)
{
    t1++;
    b1[t1]=y;//db
    next1[t1]=first1[x];
    first1[x]=t1;
    c1[t1]=z;
}
void dfs(int x,int y,double z)
{
    for(int p=first[x];p;p=next[p])
        dfs(b[p],y+1,z/sum[x]);
    if (!first[x])
        cr1(i,y,z);
}
int main()
{
    freopen("memory.in","r",stdin);
    freopen("memory.out","w",stdout);
    scanf("%d",&T);
    while (T--)
    {
        fo(i,1,m) first[i]=first1[i]=0;
        tt=t1=0;
        scanf("%d%d%d",&n,&m,&p);
        fo(i,1,m-n)
        {
            scanf("%d%d",&x,&y);
            cr(x,y);
        }
        fo(i,1,n)
        {
            sum[i]+=1;
            dfs(i,0,1);
        }
        f[n]=0;
        fd(i,n-1,1)
        {
            res=(f[i+1]+1)/sum[i];
            xs=0;
            for(p=first1[i];p;p=next1[p])
            {
                xs+=c1[p];
                res+=c1[p]*(b1[p]+1);
            }
            f[i]=res/(1-xs);
        }
        printf("%.4lf\n",f[1]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值