题目大意: 牛牛每天需要学完大学里的 n 门课程,每门课程有 2 节课 ci 和 di,这两节课会被安排在不同的教室上课。对于每门课程,牛牛可以任选一节课学习。每次上课,牛牛需要从一间教室 ai 走到另一间教室 bi,这就需要耗费他一些体力 wi。 现在,大学提供了换课的机会,对于每门课程,你可以选择换课,换课成功的概率为 ki。牛牛最多可以换 m 节课(当然他也可以选择不用完这 m 次机会)。换课之后,牛牛就可以到另外一间教室上课。 牛牛所在的大学有 v 间教室,e 条道路,每条道路连接两间教室,并且是可以双向通行的。对于每条道路,耗费的体力 wi 不一定相同。当第 i ( 1 ≤ i ≤ n−1 )节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。 现在牛牛想知道,申请这些课程中的哪节课可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。
输入: 第一行四个整数 n, m, v, e ,表示有 n 节课,m 次换课的机会,v 间教室和 e 条道路。 第二行 n 个正整数,第 i ( 1 ≤ i ≤ n )个正整数表示 ci ,即第 i 个时间段牛牛被安排上课的教室;保证 1 ≤ ci ≤ v 。 第三行 n 个正整数,第 i ( 1 ≤ i ≤ n )个正整数表示 di ,即第 i 个时间段另一间上同样课程的教室;保证 1 ≤ di ≤ v 。 第四行 n 个实数,第 i ( 1 ≤ i ≤ n )个实数表示 ki ,即牛牛申请在第 i 个时间段更换教室获得通过的概率。保证 0 ≤ ki ≤ 1 。 接下来 e 行,每行三个正整数 aj , bj , wj ,表示有一条双向道路连接教室 aj , bj ,通过这条道路需要耗费的体力值是 wj ;保证 1 ≤ aj, bj ≤ v , 1 ≤ wj ≤ 100 。
保证 1 ≤ n ≤ 2000 , 0 ≤ m ≤ 2000 , 1 ≤ v ≤ 300 , 0 ≤ e ≤ 90000。 保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。可能有两条道路连着同一间教室的情况。
输出: 输出一行,包含一个实数,四舍五入精确到小数点后恰好 2 位,表示答案。
题目分析: 由题,这道题的 n 和 m 仅为 2000,并且教室的数量 v 仅有 300。而牛牛每次要选择走最短的路到另一个教室,所以我们需要用 Floyd 求出任意两个教室之间的最短距离。 之后,根据题意,我们需要求出最后的期望移动距离,再加上 n 和 m 的数据规模的提示,我们可以使用期望 DP 的思想来解决这道题。
我们用 dp[i][j][0..1] 表示现在是第 i 门课程上课,换了 j 节课,1 表示这节课被换掉,0 表示这节课不换。
那么对于每一节课,它只有选或不选两种状态,并且可以由上一节课选或不选转移过来,因此我们可以很容易地得到 DP 方程,每个 dp[i][j][0] 对应两种状态,每个 dp[i][j][1] 也对应两种状态。
对于 dp[i][j][0] ,它可以分为两个情况:即由 dp[i−1][j][0]+dis[c[i−1]][c[i]] 和 dp[i−1][j][1]+p[i−1]∗dis[d[i−1]][c[i]]+(1−p[i−1])∗dis[c[i−1]][c[i]] 转移过来并取其最小值,其中 p 表示这节课换课成功的概率,c 在原题中表示原教室,d 表示新教室。对于 dp[i][j][1] 同理,只不过要把从哪个教室到哪个教室给换一下。具体的实现也分为两种情况,即 dp[i−1][j−1][0]+p[i]∗dis[c[i−1]][d[i]]+(1−p[i])∗dis[c[i−1]][c[i]] 和 dp[i−1][j−1][1]+p[i−1]∗(p[i]∗dis[d[i−1]][d[i]]+(1−p[i])∗dis[d[i−1]][c[i]])+(1−p[i−1])∗(p[i]∗dis[c[i−1]][d[i]]+(1−p[i])∗dis[c[i−1]][c[i]]) 。
最后,再看 dp[n][0..m][0..1] 中的最小值,最小值即为答案。
下面附上代码:
[cpp] view plain copy print ? #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MX=2005; const int MXE=305; int n,m,v,e; int c[MX],d[MX],dis[MXE][MXE]; double p[MX],dp[MX][MX][2]; void floyd(){ 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]); } void solve(){ dp[0][0][0]=0,dp[1][0][0]=0,dp[1][1][1]=0; for (int i=2;i<=n;i++){ for (int j=0;j<=m;j++){ //分为两个状态:这节课换课和这节课不换课 //每种状态对应另外两个状态:上节课换课和上节课不换课 //总共 4 种状态,如下 dp[i][j][0]=min(dp[i-1][j][0] + dis[c[i-1]][c[i]],dp[i-1][j][1] + p[i-1]*dis[d[i-1]][c[i]] + (1-p[i-1])*dis[c[i-1]][c[i]]); dp[i][j][1]=min(dp[i-1][j-1][0] + p[i]*dis[c[i-1]][d[i]] + (1-p[i])*dis[c[i-1]][c[i]],dp[i-1][j-1][1] + p[i-1]*(p[i]*dis[d[i-1]][d[i]] + (1-p[i])*dis[d[i-1]][c[i]]) + (1-p[i-1])*(p[i]*dis[c[i-1]][d[i]] + (1-p[i])*dis[c[i-1]][c[i]])); } } } int main(){ //初始化 memset(dis,0x3f,sizeof(dis)); memset(dp,0x44,sizeof(dp)); int a,b,len; 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]); for (int i=1;i<=n;i++) scanf(“%lf”,&p[i]); for (int i=1;i<=v;i++) dis[i][i]=0; for (int i=1;i<=e;i++){ scanf(”%d%d%d”,&a,&b,&len); if (a!=b){ dis[a][b]=min(dis[a][b],len); dis[b][a]=min(dis[b][a],len); } } floyd(); solve();//DP double minv=1e20; for (int i=0;i<=m;i++){ //取最小值 minv=min(min(dp[n][i][0],dp[n][i][1]),minv); } printf(”%0.2lf”,minv); return 0; }