[Codeforces 1201D]Treasure Hunting(DP)
題面
有一個n*m的方格,方格上有k個寶藏,一個人從(1,1)出發,可以向左或者向右走,但不能向下走。給出q個列,在這些列上可以向上走,其他列不能向上走。可以重復經過同一個點。求從(1,1)出發,經過所有寶藏的最短路徑長度
\(n,m,k,q \leq 2 \times 10^5\)
分析
貪心考慮,我們應該按照行一層一層的走。每一行應該從最左的寶藏走到最右的寶藏,或者從最右的寶藏走到最左的寶藏,然后找最近的一個可以向上走的列,走到另一行。
設\(dp[i][0/1]\)表示當前在第i行最左邊的寶藏,或者最右邊的寶藏,已經走完該行所有寶藏的最短距離
那么只要記錄上一個有寶藏的行pre,分類討論一下四種轉移
\(dp[i][0]\)需要先走到第i行的最右端,再從第i行的最右端走到最左端
\(dp[i][1]\)需要先走到第i行的最右端,再從第i行的最右端走到最左端
另一個問題是如何計算(x1,y1)到(x2,y2)的距離。我們預處理在第i列左邊最近的可以向上走的列lb[i],和最右邊最近的列rb[i],那么我們走的列一定是lb[y1],lb[y2],rb[y1],rb[y2]四列中的一列。
如下圖,假設y1<y2,走lb[y1]左側的一列顯然不如lb[y1]優,走rb[y2]右側的一列顯然不如rb[y2]優,而走rb[y1],lb[y2]中間的列和直接走rb[y1],lb[y2]一樣
把經過這四列向上的距離統計出來就可以了
代碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define maxn 200000
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int n,m,k,q;
vector<int>a[maxn+5];
int lb[maxn+5],rb[maxn+5];
int col[maxn+5];
ll dp[maxn+5][2];//走到最左側/最右側結束
ll calc(int y1,int y2,int line){//從y1到第line列,走上去,再到y2
return (ll)abs(y1-line)+abs(y2-line);
}
ll dist(int x1,int y1,int x2,int y2){
ll distx=abs(x1-x2);
ll disty=min(min(calc(y1,y2,lb[y1]),calc(y1,y2,lb[y2])),min(calc(y1,y2,rb[y1]),calc(y1,y2,rb[y2])));
return distx+disty;
}
int main(){
int x,y;
scanf("%d %d %d %d",&n,&m,&k,&q);
for(int i=1;i<=k;i++){
scanf("%d %d",&x,&y);
a[x].push_back(y);
}
for(int i=1;i<=q;i++){
scanf("%d",&col[i]);
}
sort(col+1,col+1+q);
col[0]=-INF;
col[q+1]=INF;
for(int i=1;i<=m;i++){
lb[i]=col[lower_bound(col+1,col+1+q,i)-col-1];
rb[i]=col[lower_bound(col+1,col+1+q,i)-col];
}
sort(a[1].begin(),a[1].end());
if(!a[1].empty()){
dp[1][0]=dp[1][1]=a[1].back()-1;
a[1][0]=a[1].back();
//第一行必須從最后一個關鍵點往上走,否則就走不完所有關鍵點
}else{
a[1].push_back(1);
dp[1][0]=dp[1][1]=0;
}
int last=1;
for(int i=2;i<=n;i++){
if(!a[i].empty()){
sort(a[i].begin(),a[i].end());
ll l_to_l=dp[last][0]+dist(last,a[last].front(),i,a[i].front());//從pre行最左側走到i行最左側
ll r_to_l=dp[last][1]+dist(last,a[last].back(),i,a[i].front()); //從pre行最右側走到i行最左側
ll l_to_r=dp[last][0]+dist(last,a[last].front(),i,a[i].back());//從pre行最左側走到i行最右側
ll r_to_r=dp[last][1]+dist(last,a[last].back(),i,a[i].back());//從pre行最右側走到i行最右側
ll now=a[i].back()-a[i].front();//走完當前行的代價
dp[i][0]=min(l_to_r,r_to_r)+now;//先走到最右側,最后到最左側
dp[i][1]=min(l_to_l,r_to_l)+now;//先走到最左側,最后到最右側
last=i;
}
}
printf("%I64d\n",min(dp[last][0],dp[last][1]));
}