P1850 换教室(期望dp,Floyd)

7 篇文章 0 订阅
5 篇文章 0 订阅

P1850 换教室

题目描述

有n个课程安排,其中第i个课程安排是在 a i a_i ai号教室上课. 每个课程安排都有相应的替代方案,也就是说,我们也可以选择申请在 b i b_i bi号教室上同样的课程. 对于每个课程安排,申请通过的概率为 p i p_i pi,而且我们最多可以申请更换 m m m个课程的上课地点. 当然,也可以选择一个都不申请. 与此同时,课间在不同教室之间移动时有一个体力消耗值,现在我们希望求一种申请更换教室的方案,使得体力消耗值的和的期望最小.

题目分析

期望的数学定义:试验中每次可能结果的概率乘以其结果的总和.

这里求的期望,等于一种已经确定的选择方案中,每一步的概率 ( P ) (P) (P)乘以每一步的体力消耗的和.

这是一个以概率加权的平均数形式.

期望dp的基本形式: E ( x ) m i n = P 1 W 1 + P 2 W 2 + . . . + P n W n E(x)_{min}=P_1W_1+P_2W_2+...+P_nW_n E(x)min=P1W1+P2W2+...+PnWn

80pts:

观察到测试点中有许多给出的是 m = 0 , m = 1 , m = 2 m=0,m=1,m=2 m=0,m=1,m=2的形式,把这些拿下就有差不多 80 p t s 80pts 80pts了.

100pts:

我们不妨考虑动态规划,设计状态: f [ i ] [ j ] f[i][j] f[i][j]表示当前 i i i个课程总共选择了 j j j个进行变更时,所得到的期望最小体力消耗.

然而我们发现这样子不足以完全表示状态,因为状态还与前一个课程是否选择更换教室有关,如果前一个课程更换了教室,那么前后两个课程的体力消耗值会改变. 也就是说,这样设的状态产生了后效性. 所幸我们知道,我们可以通过细化状态转移方程(即给状态加维)来消除后效性,那么我们就增加一维 0 / 1 0/1 0/1,表示当前这个课程是否更换了教室.

接下来我们研究如何进行状态转移.

不妨考虑最简单的情况,也就是 i − 1 i-1 i1 i i i均不申请更换,那么期望直接加上两者之间的体力消耗即可.

然后考虑 i i i不申请更换,而 i − 1 i-1 i1申请更换的情况,那么这样子 i − 1 i-1 i1就有 p i − 1 p_{i-1} pi1的可能成功更换, 1 − p i − 1 1-p_{i-1} 1pi1的可能不能成功更换,由于这两种情况都有可能发生,而期望是所有可能情况的概率乘以体力消耗的和,所以这里应该把两者和他们对应的体力消耗的积都加上.

由上面的描述,这种情况的期望是 f [ i − 1 ] [ j ] [ 1 ] + p [ i − 1 ] ∗ g [ b [ i − 1 ] ] [ a [ i ] ] + ( 1 − p [ i − 1 ] ) ∗ g [ a [ i − 1 ] ] [ a [ i ] ] f[i-1][j][1]+p[i-1]*g[b[i-1]][a[i]]+(1-p[i-1])*g[a[i-1]][a[i]] f[i1][j][1]+p[i1]g[b[i1]][a[i]]+(1p[i1])g[a[i1]][a[i]]

之后我们需要在以上两个值中取 m i n min min

f [ i ] [ j ] [ 0 ] = m i n ( f [ i − 1 ] [ j ] [ 0 ] + g [ a [ i − 1 ] ] [ a [ i ] ] , f [ i − 1 ] [ j ] [ 1 ] + p [ i − 1 ] ∗ g [ b [ i − 1 ] ] [ a [ i ] ] + ( 1 − p [ i − 1 ] ) ∗ g [ a [ i − 1 ] ] [ a [ i ] ] ) f[i][j][0]=min(f[i-1][j][0]+g[a[i-1]][a[i]],f[i-1][j][1]+p[i-1]*g[b[i-1]][a[i]]+(1-p[i-1])*g[a[i-1]][a[i]]) f[i][j][0]=min(f[i1][j][0]+g[a[i1]][a[i]],f[i1][j][1]+p[i1]g[b[i1]][a[i]]+(1p[i1])g[a[i1]][a[i]])

同理我们可以得到

f [ i ] [ j ] [ 1 ] = m i n ( f [ i − 1 ] [ j − 1 ] [ 0 ] + p [ i ] ∗ g [ a [ i − 1 ] ] [ b [ i ] ] + ( 1 − p [ i ] ) ∗ g [ a [ i − 1 ] ] [ a [ i ] ] (前一节课没有申请更换) , f [ i − 1 ] [ j − 1 ] [ 1 ] + p [ i ] ∗ p [ i − 1 ] ∗ g [ b [ i − 1 ] ] [ b [ i ] ] + p [ i ] ∗ ( 1 − p [ i − 1 ] ) ∗ g [ a [ i − 1 ] ] [ b [ i ] ] + ( 1 − p [ i ] ) ∗ p [ i − 1 ] ∗ g [ b [ i − 1 ] ] [ a [ i ] ] + ( 1 − p [ i ] ) ∗ ( 1 − p [ i − 1 ] ) ∗ g [ a [ i − 1 ] ] [ a [ i ] ] (前一节课也申请更换) ) f[i][j][1]=min(f[i-1][j-1][0]+p[i]*g[a[i-1]][b[i]]+(1-p[i])*g[a[i-1]][a[i]]\text{(前一节课没有申请更换)},f[i-1][j-1][1]+p[i]*p[i-1]*g[b[i-1]][b[i]]+p[i]*(1-p[i-1])*g[a[i-1]][b[i]]+(1-p[i])*p[i-1]*g[b[i-1]][a[i]]+(1-p[i])*(1-p[i-1])*g[a[i-1]][a[i]]\text{(前一节课也申请更换)}) f[i][j][1]=min(f[i1][j1][0]+p[i]g[a[i1]][b[i]]+(1p[i])g[a[i1]][a[i]](前一节课没有申请更换),f[i1][j1][1]+p[i]p[i1]g[b[i1]][b[i]]+p[i](1p[i1])g[a[i1]][b[i]]+(1p[i])p[i1]g[b[i1]][a[i]]+(1p[i])(1p[i1])g[a[i1]][a[i]](前一节课也申请更换))

这一个状态转移方程和上面那个的原理是一样的,只不过这里还同时用到了乘法原理(期望+=两个独立事件同时发生的概率*对应的体力消耗


讲完了状态转移方程,这道题基本上就结束了. 但是注意初始化所有状态 f f f为极大值(所有 f f f都只能由前一个时间段推知),然后 f [ 1 ] [ 0 ] [ 0 ] = 0 , f [ 1 ] [ 1 ] [ 1 ] = 0 f[1][0][0]=0,f[1][1][1]=0 f[1][0][0]=0,f[1][1][1]=0(第一节课更换教室的情况和不更换教室的情况,由于一开始就在教室,所以一开始消耗的体力均为0),枚举的当前时间段 i i i从2开始循环;在每次 i i i循环中,都先要 f [ i ] [ 0 ] [ 0 ] = f [ i − 1 ] [ 0 ] [ 0 ] + g [ a [ i − 1 ] ] [ a [ i ] ] ( i > 1 ) f[i][0][0]=f[i-1][0][0]+g[a[i-1]][a[i]](i>1) f[i][0][0]=f[i1][0][0]+g[a[i1]][a[i]](i>1)(不提出申请的情况),枚举的已用申请次数 j j j从1开始循环.

程序实现

#include<bits/stdc++.h>
using namespace std;
const int inf=400000;//inf不能开太大也不能开太小
int c[2010],d[2010],n,m,cnt,e;
int g[310][310];
double f[2010][2010][2],p[2010],ans;
int main(){
	scanf("%d%d%d%d",&n,&m,&cnt,&e);
	for(int i=1;i<=n;i++)scanf("%d",&c[i]);
	for(int i=1;i<=n;i++)scanf("%d",&d[i]);
	for(int i=1;i<=n;i++)scanf("%lf",&p[i]);
	for(int i=1;i<=cnt;i++){
		for(int j=1;j<=cnt;j++)g[i][j]=(i==j)?0:inf;
	} //初始化邻接矩阵
	for(int i=1,u,v,w;i<=e;i++){
		scanf("%d%d%d",&u,&v,&w);
		g[u][v]=min(g[u][v],w);
		g[v][u]=g[u][v];//注意双向边
	}
	for(int k=1;k<=cnt;k++){
		for(int i=1;i<=cnt;i++){
			for(int j=1;j<=cnt;j++){
				g[i][j]=min(g[i][k]+g[k][j],g[i][j]);
				g[j][i]=g[i][j];
			}
		}
	}//Floyd求最短路
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			f[i][j][0]=(double)inf;
			f[i][j][1]=(double)inf;
		}
	}//状态初始化
	f[1][0][0]=0;f[1][1][1]=0;
	for(int i=2;i<=n;i++){
		f[i][0][0]=f[i-1][0][0]+g[c[i-1]][c[i]];//每一个状态都只能由前一个推知
		for(int j=1;j<=m;j++){//从1开始循环,防止越界
			f[i][j][0]=min(f[i-1][j][0]+g[c[i-1]][c[i]],f[i-1][j][1]+p[i-1]*g[d[i-1]][c[i]]+(1-p[i-1])*g[c[i-1]][c[i]]);
			//当前课程如果不变
			f[i][j][1]=min(f[i-1][j-1][0]+p[i]*g[c[i-1]][d[i]]+(1-p[i])*g[c[i-1]][c[i]],\
			f[i-1][j-1][1]+p[i]*p[i-1]*g[d[i-1]][d[i]]+p[i]*(1-p[i-1])*g[c[i-1]][d[i]]+(1-p[i])*p[i-1]*g[d[i-1]][c[i]]+(1-p[i])*(1-p[i-1])*g[c[i-1]][c[i]]);
			//当前课程如果更换
		}
	}
	ans=inf;
	for(int i=0;i<=m;i++)ans=min(f[n][i][1],min(ans,f[n][i][0]));
	//由于没有要求m个申请全部用完,所以要比较查找
	printf("%.2lf\n",ans);
	return 0;
} 

题后总结

一篇概率/期望dp的入门博客

概率/期望dp,一定要走出一个误区:不要总是想着算出一种方案的所有情况的概率再乘权值. 要用动态规划的思维去做这些题.

比如说:已知一种方案为改变第1节课上课地点和第2节课上课地点,那我们不应该先算出对应的四种情况的概率(1成功2成功,1成功2失败,1失败2成功,1、2均失败)再去求期望,最后再比较,而是应该在状态转移中表示所有的状态.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值