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

题目大意

给出一幅 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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值