【洛谷 P3842 [TJOI2007]線段】解題報告(動態規劃)
題面
在一個 \(n\times n\) 的平面上,在每一行中有一條線段,第 \(i\) 行的線段的左端點是 \((i, L_i)\),右端點是 \((i, R_i)\)。
你從 \((1,1)\) 點出發,要求沿途走過所有的線段,最終到達 \((n,n)\) 點,且所走的路程長度要儘量短。
更具體一些說,你在任何時候只能選擇向下走一步(行數增加 \(1\))、向左走一步(列數減少 \(1\))或是向右走一步(列數增加 \(1\))。當然,由於你不能向上行走,因此在從任何一行向下走到另一行的時候,你必須保證已經走完本行的那條線段。
\(1\le n\le 2\times 10^4\),\(1\le L_i\le R_i\le n\)
題解
本題的狀態比較顯然。
由於每一層必然走過整條線段,所以顯然從線段的左/右端點向下走是最優的。我們拆分子問題,求從 \((1,1)\) 走到第 \(i\) 層線段左/右端點的最少步數,可以通過從 \((1,1)\) 走到第 \(i-1\) 層線段左/右端點的最少步數求出,因此從 \((1,1)\) 走到第 \(i\) 層線段左/右端點的最少步數就是一個子問題。
根據我們得到的子問題,我們設狀態 \(dp_{i,[0/1]}\) 表示從 \((1,1)\) 走到第 \(i\) 層線段的左/右端點的最少步數。以從 \(dp_{i-1,0}\) 向 \(dp_{i,1}\) 轉移為例(也就是從上一層線段左端點走到這一層線段右端點),我們首先需要橫向走到這一層線段左端點,將橫座標從 \(L_{i-1}\)
最終我們還需要從 \(L_n\) 或 \(R_n\) 走到 \((n,n)\),可以按照類似的方法求出 \(ans\gets\min\{dp_{n,0}+n-L_n,dp_{n,1}+n-R_n\}\)
初始狀態就是從 \((1,1)\) 到 \(R_1\) 的距離,或者到 \(R_1\) 再到 \(L_1\) 的距離。
參考程式碼
//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
using namespace std;
typedef long long ll;
const int N = 2e4+5;
int n, L[N], R[N], dp[N][2];
template<typename T> void chkmin(T &x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T &x, T y) {if(x < y) x = y;}
int main() {
scanf("%d", &n);
rep(i, 1, n) scanf("%d%d", &L[i], &R[i]);
dp[1][0] = 2 * R[1] - L[1] - 1;
dp[1][1] = R[1] - 1;
rep(i, 2, n) {
dp[i][0] = min(dp[i-1][0]+abs(L[i-1]-R[i])+R[i]-L[i],
dp[i-1][1]+abs(R[i-1]-R[i])+R[i]-L[i]) + 1;
dp[i][1] = min(dp[i-1][0]+abs(L[i-1]-L[i])+R[i]-L[i],
dp[i-1][1]+abs(R[i-1]-L[i])+R[i]-L[i]) + 1;
}
int ans = min(dp[n][0]+n-L[n], dp[n][1]+n-R[n]);
printf("%d\n", ans);
return 0;
}
博文作者:rui_er。本部落格所有公開(無密碼)博文可根據 CC BY-NC-SA 4.0 協議進行轉載,非公開博文可與作者聯絡獲得許可後轉載。