题目:洛谷P1850、UOJ#262、BZOJ4720、Vijos P2005。
题目大意:有n个时间段,第i个时间段只能在教室$c_i$上课,另一个上这门课的教室在$d_i$。现在你最多可以进行m次申请,对于第i个时间段的申请如果成功,那么就能在$d_i$教室上课,但成功率为$p_i$。且教室与教室之间用e条双向道路连接,每条路有一个耗费体力的值。问申请哪几门课程可以使因在教室间移动耗费的体力值的总和的期望值最小。
解题思路:这是一道期望dp问题。对于每一时间段(第一时间段除外),我们可以分为这么几种情况。
①该时间段不申请:
1.上时间段申请 成功/失败
2.上时间段不申请
②该时间段申请:
1.该时间段申请 成功/失败 and 上时间段申请 成功/失败
2.上时间段不申请
设f[i][j][0], f[i][j][1]表示当前在第i个时间段,申请了j个教室,0表示申请不通过,1表示申请通过,最小耗费体力的期望值。
dis[i][j]表示教室i到教室j的最短路。
综合以上几点情况,可得以下状态转移方程:
$f(i,j,0)=min(f(i-1,j,0)+dis(c_{i-1},c_i),f(i-1,j,1)+dis(d_{i-1}+c_i)*p_{i-1}+dis(c_{i-1},c_i)*(1-p_{i-1}))$
$f(i,j,1)=min(f(i-1,j-1,0)+dis(c_{i-1},d_i)*p_i+dis(c_{i-1},c_i)*(1-p_i),f(i-1,j-1,1)+dis(d_{i-1},d_i)*p_{i-1}*p_i+dis(d_{i-1},c_i)*p_{i-1}*(1-p_i)+dis(c_{i-1},d_i)*(1-p_{i-1})*p_i+dis(c_{i-1},c_i)*(1-p_{i-1})*(1-p_i))$
dis用floyd求出,注意dis[i,i]=0
那么时间复杂度为$O(v^3+nm)$。空间复杂度为$O(v^2+nm)$,可以用滚动数组优化为$O(v^2+m)$。
C++ Code:
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,v,e,c[2002],d[2002],dis[302][302];
double p[2002],f[2][2002][2];
template<typename T>
inline T min(T a,T b){return(a<b)?a:b;}
int main(){
scanf("%d%d%d%d",&n,&m,&v,&e);
memset(dis,0x3f,sizeof dis);
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]);
while(e--){
int u,v,t;
scanf("%d%d%d",&u,&v,&t);
dis[u][v]=dis[v][u]=min(dis[u][v],t);
}
for(int i=1;i<=v;++i)dis[i][i]=0;
for(int k=1;k<=v;++k)
for(int i=1;i<=v;++i)
if(i!=k)
for(int j=1;j<=v;++j)
if(i!=j&&j!=k)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
memset(f,0x7f,sizeof f);
f[1][0][0]=f[1][1][1]=0;
for(int i=2;i<=n;++i){
memset(f[i&1],0x7f,sizeof f[i&1]);
f[i&1][0][0]=f[i&1^1][0][0]+dis[c[i-1]][c[i]];
for(int j=1;j<=i&&j<=m;++j){
f[i&1][j][0]=min(f[i&1^1][j][0]+dis[c[i-1]][c[i]],
f[i&1^1][j][1]+dis[d[i-1]][c[i]]*p[i-1]+dis[c[i-1]][c[i]]*(1-p[i-1]));
f[i&1][j][1]=min(f[i&1^1][j-1][0]+dis[c[i-1]][d[i]]*p[i]+dis[c[i-1]][c[i]]*(1-p[i]),
f[i&1^1][j-1][1]+dis[d[i-1]][d[i]]*p[i-1]*p[i]+dis[d[i-1]][c[i]]*p[i-1]*(1-p[i])+
dis[c[i-1]][d[i]]*(1-p[i-1])*p[i]+dis[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i]));
}
}
double ans=f[n&1][0][0];
for(int i=1;i<=m;++i){
ans=min(ans,f[n&1][i][0]);
ans=min(ans,f[n&1][i][1]);
}
printf("%.2f\n",ans);
return 0;
}