题目链接:https://www.luogu.org/problemnew/show/P1850
一道难度不大的dp(就是状态和转移都很好想),但如果要想A掉的话必须要知道期望的一个性质。即:E(X+Y)=E(X)+E(Y),说白了就是期望具有可加性,根据这个性质我们可以进行转移,转移很好想,就是有些麻烦。
状态就是dp[i][j][1]表示当前到了第i个时间点,换了j次,且当前这次也换了;dp[i][j][0]表示当前到了第i个时间点,换了j次,然后这次没有换。
转移方程直接看代码吧。
然后个人觉得这道题最难的地方就是边界的处理,就是我们根据什么来进行递推。这里的技巧就是由于我们要求最小值,所以一开始可以把dp数组初始化为INF,这样在递推时可以防止一些不合常理的状态的排除。
具体看代码吧。
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int T=2e3+10, C=2e3+10, N = 3e2+10, M = 9e4+10;
const double INF=1e9;
int n, m, v, e, c[T], d[T];
double ans=INF, k[T], map[N][N], dp[T][C][2];
void init() {
int i, j;
for (i=1; i<=v; i++)
for (j=1; j<=v; j++)
if (i==j) map[i][j]=0;
else map[i][j]=INF;
for (i=1; i<=n; i++)
for (j=0; j<=m; j++)
dp[i][j][0]=dp[i][j][1]=INF;
return ;
}
void Floyed() {
int i, j, k;
for (k=1; k<=v; k++)
for (i=1; i<=v; i++)
for (j=1; j<=v; j++)
map[i][j]=min(map[i][j], map[i][k]+map[k][j]);
return ;
}
int main() {
int i, j, x, y;
double z;
cin >> n >> m >> v >> e;
init();
for (i=1; i<=n; i++)
cin >> c[i];
for (i=1; i<=n; i++)
cin >> d[i];
for (i=1; i<=n; i++)
cin >> k[i];
for (i=1; i<=e; i++) {
cin >> x >> y >> z;
map[x][y]=min(map[x][y], z);
map[y][x]=min(map[y][x], z);
}
Floyed();
dp[1][1][1]=0, dp[1][0][0]=0;
for (i=2; i<=n; i++) {
for (j=0; j<=min(i,m); j++) {
//下面是转移方程
dp[i][j][0]=min(dp[i-1][j][0]+map[c[i-1]][c[i]], dp[i-1][j][1]+map[d[i-1]][c[i]]*k[i-1]+map[c[i-1]][c[i]]*(1-k[i-1]));
if (j==0) continue;//这个特判很重要,保证了dp时状态的合理
//另一个转移方程
dp[i][j][1]=min(dp[i-1][j-1][0]+map[c[i-1]][d[i]]*k[i]+map[c[i-1]][c[i]]*(1-k[i]), dp[i-1][j-1][1]+map[d[i-1]][d[i]]*k[i]*k[i-1]+map[c[i-1]][d[i]]*k[i]*(1-k[i-1])+map[d[i-1]][c[i]]*(1-k[i])*k[i-1]+map[c[i-1]][c[i]]*(1-k[i])*(1-k[i-1]));
}
}
for (i=0; i<=m; i++)
ans=min(min(dp[n][i][0], dp[n][i][1]), ans);
printf("%.2lf", ans);
return 0;
}
总结:DP的题目转移时一定要考虑全面,否则会出现很离谱的结果,甚至爆零。