昨天做的一道蓝桥杯国赛真题,一开始没想到DP的状态转移,暴力debug了一晚上,跑了30分,今天研究了一下DP解法,从他给的样例入手先画个图。
测试样例应该就是这么走的,有可能走斜线,有可能走直线,因为两边的画是错开放的,所以从左到右一定是斜线。根据勾股定理可以写个函数来求。那状态怎么定义呢?dp[i][j]表示左边i个画完成右边j个画完成的最短距离,但是就拿dp[1][1]来说,左右各完成一个,他此时有可能在左边,也有可能在右边,我们没办法确认他在左边还是右边,缺一种状态,所以多加一种状态,0表示左边,1表示右边,用dp[i][j][0]和dp[i][j][1]来表示状态。
这样其实转移方程并不难想,应该是——
dp[i][j][0]=min(dp[i][j][0],min(dp[i-1][j][0]+l[i]-l[i-1],dp[i-1][j][1]+slash(l[i],r[j],w)));
dp[i-1][j][0]表示:左边i-1个完成了,右边j个完成了,此时在左边,那么它到达dp[i][j][0]的方式应该是走直线,就加上两点之间的直线距离。
dp[i-1][j][1]表示:左边i-1个完成了,右边j个完成了,此时在右边,那么它到达dp[i][j][0]的方式应该是走斜线,就加上斜线的距离。
初始化:初始确定的状态应该就两种,第一种是dp[1][0][0],因为只完成左边第一幅画此时一定在左边,不可能在右边。第二种就是dp[0][1][1],因为只完成右边第一幅画一定在右边。
然后注意要把dp数组用memset全部制成极大值,保证不存在的状态例如:dp[0][3][0]这种东西不会影响到状态转移。(左边没有完成一幅画不可能在左边)
所以状态转移的时候也要多套一层min,不然初始的状态会受影响(可能被不存在的状态更新成不存在的值);
附上memset的妙用:
#include <string.h>
int a[100];
memset(map,0,sizeof(map)); //全为0
memset(map,-1,sizeof(map)); //全为-1
memset(map,127,sizeof(map)); //全为无穷大
memset(map,128,sizeof(map)); //全为无穷小
AC代码如下:
#include<bits\stdc++.h>
using namespace std;
double slash(double x,double y,double w){
return sqrt((x-y)*(x-y)+w*w);
}
double l[505],r[505];
double dp[505][505][2];
int main(){
int L,R;
double d,w;
cin>>L>>R>>d>>w;
memset(dp,127,sizeof(dp));
for(int i=1;i<=L;++i)cin>>l[i];
for(int i=1;i<=R;++i)cin>>r[i];
dp[1][0][0]=slash(l[1],0,w/2.00);
dp[0][1][1]=slash(r[1],0,w/2.00);
for(int i=0;i<=L;i++){
for(int j=0;j<=R;j++){
if(i)dp[i][j][0]=min(dp[i][j][0],min(dp[i-1][j][0]+l[i]-l[i-1],dp[i-1][j][1]+slash(l[i],r[j],w)));
if(j)dp[i][j][1]=min(dp[i][j][1],min(dp[i][j-1][1]+r[j]-r[j-1],dp[i][j-1][0]+slash(l[i],r[j],w)));
}
}
double ans=min(dp[L][R][0]+slash(d,l[L],w/2.00),dp[L][R][1]+slash(d,r[R],w/2.00));
printf("%.2f",ans);
return 0;
}