【NOIP2016 D1T3】换教室(期望DP+Floyd)(究极思维陷阱!)

版权声明:本文为博主原创文章,请随意转载(注明出处)。 https://blog.csdn.net/can919/article/details/82666446

题目大意

给出一幅 v 个点的无向图,表示教室及其连边。
有 n 个时刻,每个时刻正常要到教室 c[i] 上课,如果该时刻有申请更换,则到教室 d[i] 上课。
你只能在一切开始之前提交申请,且最多申请换 m 个时刻。第 i 个时刻申请成功的概率为 k[i]。
求移动路程的期望最小值。

题解

首先用floyd把任意两点
定义dp[i][j][0/1]为从第i个时刻之后开始,还剩j个申请可用,当前时刻是否已申请,走到最后的期望路程。
转移时,走一步,将状态走向i+1,分几种情况
当前未申请,只能从c[i]出发,下一个可申请或不申请,申请后分成功和失败

dp[i][j][0]=min(dp[i+1][j][0]+dis[c[i]][c[i+1]]
                ,dp[i+1][j-1][1]+dis[c[i]][d[i+1]]*k[i+1]+dis[c[i]][c[i+1]]*(1-k[i+1])

当前申请,有可能从c[i]出发,也有可能从d[i]出发

dp[i][j][1]=min(dp[i+1][j][0]+dis[c[i]][c[i+1]]*(1-k[i])+dis[d[i]][c[i+1]]*k[i],
                ,dp[i+1][j-1][1]+dis[c[i]][c[i+1]]*(1-k[i])*(1-k[i+1])+dis[c[i]][d[i+1]]*(1-k[i])*k[i+1]+dis[d[i]][c[i+1]]*k[i]*(1-k[i+1])+dis[d[i]][d[i+1]]*k[i]*k[i+1])

结果就是i=1中dp最小值

严重的思维陷阱

容易把状态定义为dp[i][j][0/1]表示为从第i个时刻之后开始,还剩j个申请可用,当前时刻的教室是否已更换,转移看似很简单(还能过大样例),但实际上有漏洞
因为已经确定了当前这一位的位置,转移就不会有上面那么长

当p=0,令u=c[i]
当p=1,令u=d[i]
dp[i][j][p]=min(dp[i+1][j][0]+dis[u][c[i+1]]//不申请
                ,k[i+1]*(dp[i+1][j-1][1]+dis[u][d[i+1]])+(1.0-k[i+1])*(dp[i+1][j-1][0]+dis[u][c[i+1]]))//申请i+1,分成功和失败

最优期望的定义是:在确定的最优方案下,所有情况的权值乘以概率的和
而这个dp转移,申请i+1时,分为了dp[i+1][j-1][1]的最优期望和dp[i+1][j-1][0]的最优期望,而这两个最优期望得到的申请方案并不相同,不能用来计算当前的最优期望(把两种非随机的策略的权值乘以概率再加起来,明显错误)

而正确的DP,每个状态只会从一个状态转移过来,再分各种情况,加上他们权值乘以概率。

简单来说,正确的DP,是可以通过转移来输出最优方案的,而错误的DP,却已一种神奇的方式,把多种方案用概率结合在一起,是WA的。。。o(╥﹏╥)o

震惊的是,错误的DP居然有88分!!!

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=2005,MAXV=305,MAXE=900005;

int n,m,v,e;
int dis[MAXV][MAXV];
int c[MAXN],d[MAXN];
double k[MAXN];
double dp[MAXN][MAXN][2];

int main()
{
    freopen("classroom.in","r",stdin);
    freopen("classroom.out","w",stdout);

    scanf("%d%d%d%d",&n,&m,&v,&e);
    for(int i=1;i<=n;i++)
        scanf("%d",&c[i]);
    for(int i=1;i<=n;i++)
        scanf("%d",&d[i]);
    c[0]=d[0]=0;
    for(int i=1;i<=n;i++)
        scanf("%lf",&k[i]);
    memset(dis,0x3F,sizeof dis);
    for(int i=1;i<=v;i++)
        dis[0][i]=dis[i][i]=0;
    for(int i=1;i<=e;i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        dis[a][b]=min(dis[a][b],w);
        dis[b][a]=min(dis[b][a],w);
    }

    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<=v;j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    //fprintf(stderr,"%d %d %d %d",dis[73][22],dis[73][58],dis[82][22],dis[82][58]);
    for(int i=n-1;i>=1;i--)
        for(int j=0;j<=m;j++)
        {
            dp[i][j][0]=dp[i+1][j][0]+dis[c[i]][c[i+1]];
            if(j>0)
                dp[i][j][0]=min(dp[i][j][0],dp[i+1][j-1][1]+dis[c[i]][d[i+1]]*k[i+1]+dis[c[i]][c[i+1]]*(1-k[i+1]));
            //fprintf(stderr,"dp[%d][%d][%d]=%f\n",i,j,0,dp[i][j][0]);
            dp[i][j][1]=dp[i+1][j][0]+dis[c[i]][c[i+1]]*(1-k[i])+dis[d[i]][c[i+1]]*k[i];
            if(j>0)
                dp[i][j][1]=min(dp[i][j][1],dp[i+1][j-1][1]+dis[c[i]][c[i+1]]*(1-k[i])*(1-k[i+1])+dis[c[i]][d[i+1]]*(1-k[i])*k[i+1]+dis[d[i]][c[i+1]]*k[i]*(1-k[i+1])+dis[d[i]][d[i+1]]*k[i]*k[i+1]);
            //fprintf(stderr,"dp[%d][%d][%d]=%f\n",i,j,1,dp[i][j][1]);
        }
    double ans=1e100;
    for(int j=0;j<=m;j++)
        ans=min(ans,min(dp[1][j][0],m>j?dp[1][j][1]:1e100));
    printf("%.2f\n",ans);

    fclose(stdin);
    fclose(stdout);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/can919/article/details/82666446