[NOIP2016] 换教室
换教室
题目大意
有 \(2n\) 节课程被安排在 \(n\) 个时间段上。
每个时间段有两个在不同教室进行的相同课程,教室之间存在一定的距离。
如果两节课在不同的教室,当前课程结束后,你将会前往选择一条耗费体力最少的路径前往下一个教室。
原则上,你每个时间段的课程的教室已经被分配好了,记为 \(c_i\) ,每个时间段教授相同课程的教室记为 \(d_i\) ,你可以换 \(m\) 次教室,但是每次换教室有一个概率 \(p_i\) ,即你只有 \(p_i\) 的概率能够更换第 \(i\) 个时段的教室。
求出移动距离的最小期望。
分析
比较显然的一道 \(dp\) 题,最开始设置的状态是 \(dp[i][j]\) 表示前 \(i\) 个时间段换了 \(j\) 次教室,但状态转移方程不是很好写,发现我们的转移少了一维参照,我们需要知道当前的时段我们是否进行换教室。
于是我们设计出了第二种状态 \(dp[i][j][0/1]\) 表示前 \(i\) 个时间段换了 \(j\) 次教室当前时间段是否换教室。
则我们可以写出转移方程:
\[dp[i][j][0]=min(dp[i-1][j][0]+dis[c[i-1]][c[i]],dp[i-1][j][1]+p[i-1]\times dis[d[i-1]][c[i]]+(1-p[i-1])\times dis[c[i-1]][c[i]])
\]
\[dp[i][j][1]=min(dp[i-1][j-1][0]+p[i]\times dis[c[i-1]][d[i]]+dis[c[i-1]][c[i]]\times (1-p[i]),dp[i-1][j-1][1]+dis[c[i-1]][c[i]]\times (1-p[i-1])\times (1-p[i])+dis[c[i-1]][d[i]]\times (1-p[i-1])\times p[i]+dis[d[i-1]][c[i]]\times (1-p[i])\times p[i-1]+dis[d[i-1]][d[i]]\times p[i-1]\times p[i])
\]
其中, \(dis[i][j]\) 表示 \(i\) 到 \(j\) 的最短距离,我们可以使用 \(Floyd\) 算法求得, \(O(v^3)\) 的时间复杂度显然可以接受。
稍微解释一下这个转移方程吧,以当前不换教室的转移为例,显然,我们可以从上一个换和上一个不换两种状态更新过来,上一个不换的状态比较好更新,直接加上距离即可。若是从上一个换的状态更新过来的,就需要考虑更换成功与否的概率,再分别乘上各自对应的路程。
当前换教室同理,只是会增加多种情况讨论,此处不再赘述,懒。
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e3+10,M=3e2+10;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline double min(double a,double b) { return a<b?a:b; }
int n,m,v,e;
int c[N*10],d[N*10];
double ans,p[N*10],dp[N][N][2],dis[M*10][M*10];
signed main()
{
//freopen("P1850_1 (1).in","r",stdin);
n=read(),m=read(),v=read(),e=read();
for(register int i=1;i<=n;i++) c[i]=read();
for(register int i=1;i<=n;i++) d[i]=read();
for(register int i=1;i<=n;i++) cin>>p[i];
for(register int i=1;i<=v;i++) for(register int j=1;j<i;j++) dis[i][j]=dis[j][i]=999999999;
for(register int i=1;i<=e;i++){
int x=read(),y=read(),z=read();
dis[x][y]=dis[y][x]=min(dis[x][y],z);
}
//处理两间教室间的最短路
for(register int k=1;k<=v;k++)
for(register int i=1;i<=v;i++)
for(register int j=1;j<i;j++)
if(dis[i][k]+dis[k][j]<dis[i][j]) dis[i][j]=dis[j][i]=dis[i][k]+dis[k][j];
//cout<<dis[1][2]<<endl;
for(register int i=1;i<=n;i++) for(register int j=0;j<=m;j++) dp[i][j][0]=dp[i][j][1]=999999999;
dp[1][0][0]=dp[1][1][1]=0;
for(register int i=2;i<=n;i++){ //枚举时间点
for(register int j=0;j<=min(m,i);j++){ //枚举换教室数量
dp[i][j][0]=min(dp[i-1][j][0]+dis[c[i-1]][c[i]],dp[i-1][j][1]+dis[d[i-1]][c[i]]*p[i-1]+dis[c[i-1]][c[i]]*(1-p[i-1]));
if(j) dp[i][j][1]=min(dp[i-1][j-1][0]+dis[c[i-1]][d[i]]*p[i]+dis[c[i-1]][c[i]]*(1-p[i]),dp[i-1][j-1][1]+dis[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i])+dis[c[i-1]][d[i]]*(1-p[i-1])*p[i]+dis[d[i-1]][c[i]]*(1-p[i])*p[i-1]+dis[d[i-1]][d[i]]*p[i-1]*p[i]);
}
}
ans=9999999999;
for(register int i=0;i<=m;i++){
ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
//cout<<dp[n][i][0]<<" "<<dp[n][i][1]<<endl;
}
printf("%.2lf",ans);
return 0;
}