题解 P4009 【汽车加油行驶问题】 DP做法

wjyyy

2018-07-05 16:26:41

Solution

[欢迎来我的blog食用](http://www.wjyyy.top/728.html) ### 它真的是网络流24题吗 本题要求从左上角走到右下角,并且还会有强制消费,因此我们根据状态**递推**。为了保证最优解,我们是可以不理会它的强制消费转而向左或向上走,但是这样可能会有**时间不同步**的情况,不方便处理,所以我们试着把这个过程**分层**。类似广搜,实则**DP**,我们每次**只走一步**更新状态是最稳妥的。而对于每个状态取的都是**最小值**,所以引出了**DP**的思想。 对于每个点来说,我们一旦出发,**时间轴**就会向前推进,为了保证状态正确,我们额外开一维使得状态只能来**自同时的相邻的四个点**,也就是**分层**的思想。那么一直推下去,理论最坏复杂度为$N^5$,路径长度可能达到$N^2$甚至更多,而根据考试策略,对于最大情况100×100控制在1s以内(最好是0.8s左右),来控制分层图的层数。于是我就定了分层为一个**固定的不会爆1s**的常数~~(经测试33333可能比较符合我的常数)~~除以n的大小,这样既保证了小数据的正确性(此时n较小),又可以碰碰运气看较大数据是否能跑出正确解。 这个题还是有一些细节需要注意的,不仅是判断加油或绕路,而且还可以在**任何想建立**加油站的地方建立加油站,然后更新每个状态取最小值就可以了,只要**方程中**状态相同,从这里怎么转移出去都是一样的,就有点像**DP**的做法了。 实际上,由于最短路径长度为2n-1,加油费用与向上/左绕路在变动较大时可能是**加油更优**,那么我们基本可以推测到最短路径的长度在4n以内(虽然题目没给A,B,C的范围),考试时为了稳妥还是稍微卡一下时好了。转移方程也是比较清晰的~~,就是状态比较多**而已**~~。 ## Code: ```cpp #include<cstdio> #include<cstring> int min(int x,int y) { return x<y?x:y; } int f[2][105][105][12];//第一维滚动 二三维坐标 第四维是还剩多少油 int g[105][105]; int ans=0x7fffffff; int main() { memset(f,0x3f,sizeof(f)); int n,k,a,b,c; scanf("%d%d%d%d%d",&n,&k,&a,&b,&c); f[0][1][1][k]=0;//初始化 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&g[i][j]); int up=33333/n; for(int t=1;t<=up;t++)//这是一个不爆时间的范围 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(g[i][j]==0)//没有强制消费 { for(int l=1;l<=k;l++) { f[t&1][i][j][l-1]=0x3f3f3f3f;//滚动数组优化时间(也可以不用这一行,因为越到后面越费钱) if(i>1) f[t&1][i][j][l-1]=min(f[t&1][i][j][l-1],f[t&1^1][i-1][j][l]); if(i<n) f[t&1][i][j][l-1]=min(f[t&1][i][j][l-1],f[t&1^1][i+1][j][l]+b); if(j>1) f[t&1][i][j][l-1]=min(f[t&1][i][j][l-1],f[t&1^1][i][j-1][l]); if(j<n) f[t&1][i][j][l-1]=min(f[t&1][i][j][l-1],f[t&1^1][i][j+1][l]+b); if(i==j&&j==n)//更新答案 ans=ans<f[t&1][i][j][l-1]?ans:f[t&1][i][j][l-1]; } if(i>1) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i-1][j][1]+c+a); if(i<n) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i+1][j][1]+c+a+b); if(j>1) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i][j-1][1]+c+a); if(j<n) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i][j+1][1]+c+a+b); if(i==j&&j==n) ans=ans<f[t&1][i][j][k]?ans:f[t&1][i][j][k]; } else//有强制消费 { for(int l=1;l<=k;l++) { if(i>1) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i-1][j][l]+a); if(i<n) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i+1][j][l]+a+b); if(j>1) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i][j-1][l]+a); if(j<n) f[t&1][i][j][k]=min(f[t&1][i][j][k],f[t&1^1][i][j+1][l]+a+b); } } } printf("%d\n",ans); return 0; } ```