题目描述
有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 i−1, i i i均不申请更换,那么期望直接加上两者之间的体力消耗即可.
然后考虑 i i i不申请更换,而 i − 1 i-1 i−1申请更换的情况,那么这样子 i − 1 i-1 i−1就有 p i − 1 p_{i-1} pi−1的可能成功更换, 1 − p i − 1 1-p_{i-1} 1−pi−1的可能不能成功更换,由于这两种情况都有可能发生,而期望是所有可能情况的概率乘以体力消耗的和,所以这里应该把两者和他们对应的体力消耗的积都加上.
由上面的描述,这种情况的期望是 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[i−1][j][1]+p[i−1]∗g[b[i−1]][a[i]]+(1−p[i−1])∗g[a[i−1]][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[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 ] [ 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[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 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[i−1][0][0]+g[a[i−1]][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,一定要走出一个误区:不要总是想着算出一种方案的所有情况的概率再乘权值. 要用动态规划的思维去做这些题.
比如说:已知一种方案为改变第1节课上课地点和第2节课上课地点,那我们不应该先算出对应的四种情况的概率(1成功2成功,1成功2失败,1失败2成功,1、2均失败)再去求期望,最后再比较,而是应该在状态转移中表示所有的状态.