noip2016换教室
一道困扰了我很久的题,好多次的刷题计划里都自动略过这道,一部分原因是因为这题目名字和noip2012借教室实在过于相像,另一部分原因,是这道题看起来实在太复杂了......
但noipCSP迫在眉睫,于是我开始做这题,做着做着,感觉海星,有点上头(x
不说了,来看题吧
1.题目解析
题目大意是,现在有一张图,牛牛每个时间段会从给定的一个点走到另一个点,每个时间段内他可以选择申请改去另一个点,但这种申请只能进行至多m次。每次申请有失败的可能,并且申请失败依旧会算入总数中。已知每次申请成功的概率,求期望走到的最短路。
首先,我们要解决一个问题,什么是期望?期望又可以称为带权平均数,具体可以查看百度百科(这里)。它和求概率的最大差别就是期望在求值的时候会算上权值。可能有点不清楚,让我举个栗子解释一下。
在这道题中,假设现在的选择是从i走到j,申请改变到x,那么这段路的期望值就是a[i][x]*p[i](当前最短路乘以申请成功的概率)+a[i][j]*(1-p[i])(申请不成功的情况),这里p[i]的求值会在后文里提到。
明白了吗,接下来,我们就要开始思考该如何解题了
2.一些处理
我们首先要求出几个点之间的最短路,由于数据量并不是很大,在所有方法里随便选一个就好,我直接打了最简单的 floyed。因为要多次取用,建议直接使用邻接矩阵(其实就是懒)。当然如果你想用 Dijkstra 或者别的算法的话,那也可以,随便......
这部分直接粘板子上去就可以。
void floyed() { for(int k=1;k<=v;k++) for(int i=1;i<=v;i++) for(int j=1;j<i;j++) if(f[i][k]+f[k][j]<f[i][j]) { f[i][j]=f[i][k]+f[k][j]; f[j][i]=f[i][k]+f[k][j]; } }
记得要先给 f 数组赋好极大值,否则在后来进行决策的时候会被判断成可以通过(因为中间是取最小值来着)
然后是 dp 方程的状态。
首先我们需要用 dp 方程记录当前时间点和换教室的次数,于是先设置两维 i 和 j 代表当前这次转移是 i 时间点时已经申请了 j 次交换。又由于每次进行转移的时候,都有申请更换和不申请更换两种选择,所以咱们再添加一维 k 代表此时是否申请了更换。
所以最后的 dp 数组就是 dp[i][j][k] 代表 i 时间时已经更换了 j 次教室并且当前的更换状态是 k(0或1)。
下面让我们来推导 dp 的转移方程。
3. dp 方程推导
这道题里最麻烦的部分就是 dp 方程。各种状态真的写疯我......
来分成几个部分看一下。
对于每一个点,我们都可以枚举当前换了 j 次教室的情况。所以两重枚举分别如下:
for(int i=2;i<=n;i++) { for(int j=0;j<=min(i,m);j++)//当 i 小于 m 的时候,最多更换 i 次,所以可以进行剪枝 { } }
对于每个dp状态,都有换和不换两种情况。
如果不换的话,那么当前的最短路径就是上一次更换或不更换的最小值。上一次换,这次便应该是从 d[i-1] 走到 c[i]。上一次不换就会麻烦一点,需要计算申请成功和不成功的期望和,方程用语言描述比较麻烦,直接看具体转移。
dp[i][j][0]=min(dp[i-1][j][0]+f[c[i-1]][c[i]],dp[i-1][j][1]+f[d[i-1]][c[i]]*k[i-1]+f[c[i-1]][c[i]]*(1-k[i-1]));
如果换,那考虑的就是四种情况。
换成功,上一次换了
换失败,上一次换了
换成功,上一次没换
换失败,上一次没换
接下来迎来本代码最长的一句话
dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*k[i]+f[c[i-1]][c[i]]*(1-k[i]),dp[i-1][j-1][1]+f[d[i-1]][d[i]]*k[i]*k[i-1]+f[d[i-1]][c[i]]*(1-k[i])*k[i-1]+f[c[i-1]][d[i]]*(1-k[i-1])*k[i]+f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]));
很 不 友 好
然后因为换教室的次数只要小于给定次数就可以满足,所以我们给所有可能取一个最小值,即可得出答案。
代码
#include<bits/stdc++.h> using namespace std; int c[2005],d[2005]; double k[2005],dp[2005][2005][2],f[2005][2005]; int n,m,v,e; void floyed() { for(int k=1;k<=v;k++) for(int i=1;i<=v;i++) for(int j=1;j<i;j++) if(f[i][k]+f[k][j]<f[i][j]) { f[i][j]=f[i][k]+f[k][j]; f[j][i]=f[i][k]+f[k][j]; } } int main() { // freopen("P1850_18.in","r",stdin); // freopen("P1850.out","w",stdout); cin>>n>>m>>v>>e; for(int i=1;i<=n;i++) cin>>c[i]; for(int i=1;i<=n;i++) cin>>d[i]; for(int i=1;i<=n;i++) cin>>k[i]; for(int i=1;i<=v;i++) for(int j=1;j<i;j++) f[i][j]=f[j][i]=2147483640; for(int i=1;i<=e;i++) { int x,y; double z; cin>>x>>y>>z; f[x][y]=min(f[x][y],z); f[y][x]=min(f[y][x],z); } floyed(); for(int i=1;i<=n;i++) { for(int j=0;j<=m;j++) { dp[i][j][0]=2147483640; dp[i][j][1]=2147483640; } } dp[1][0][0]=0;dp[1][1][1]=0; for(int i=2;i<=n;i++) { //double road=f[c[i-1]][c[i]]; for(int j=0;j<=min(i,m);j++) { dp[i][j][0]=min(dp[i-1][j][0]+f[c[i-1]][c[i]],dp[i-1][j][1]+f[d[i-1]][c[i]]*k[i-1]+f[c[i-1]][c[i]]*(1-k[i-1])); if(j!=0) dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*k[i]+f[c[i-1]][c[i]]*(1-k[i]),dp[i-1][j-1][1]+f[d[i-1]][d[i]]*k[i]*k[i-1]+f[d[i-1]][c[i]]*(1-k[i])*k[i-1]+f[c[i-1]][d[i]]*(1-k[i-1])*k[i]+f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i])); } } double minn=2147483640; for(int i=0;i<=m;i++) { minn=min(dp[n][i][0],min(dp[n][i][1],minn)); } printf("%.2f",minn); return 0; }