P3842 [TJOI2007]线段
题目描述
在一个 (n imes n) 的平面上,在每一行中有一条线段,第 (i) 行的线段的左端点是 ((i, L(i))),右端点是 ((i, R(i))),其中 (1leq L(i)leq R(i)leq n)。
你从 ((1, 1)) 点出发,要求沿途走过所有的线段,最终到达 ((n, n)) 点,且所走的路程长度要尽量短。
更具体一些说,你在任何时候只能选择向下走一步(行数增加 (1))、向左走一步(列数减少 (1))或是向右走一步(列数增加 (1))。当然,由于你不能向上行走,因此在从任何一行向下走到另一行的时候,你必须保证已经走完本行的那条线段。
输入格式
输入文件的第一行有一个整数 (n),以下 (n) 行,在第 (i) 行(总第 (i+1) 行)的两个整数表示 (L(i)) 和 (R(i))。
输出格式
输出文件仅包含一个整数,你选择的最短路程的长度。
输入输出样例
输入
6
2 6
3 4
1 3
1 2
3 6
4 5
输出
24
说明/提示
我们选择的路线是
((1,1)->(1,6))
((2,6)->(2,3))
((3,3)->(3,1))
((4,1)->(4,2))
((5,2)->(5,6))
((6,6)->(6,4)->(6,6))
不难计算得到,路程的总长度是 (24)。
(100\%) 的数据中,(nleq 20,000)。
思路
不难看出,路程长度由两个部分组成:左右走的距离 (+) 上下走的距离,因为上下走的距离一定是 (n),所以我们只需要求出左右走的最小距离即可。
很明显,当我们走完一行的线段时,我们会处在左端点/右端点,这样我们就需要在线性 (dp) 的基础上加一维来表示走完这一行处在左/右端点。
所以我们定义一个 (f[i][0/1]),表示走完第 (i) 行的线段后,我们处在左/右端点。
然后我们就可以用上一行的状态来推出下一行了。
分为 (4) 种情况:
上一行在左端点,下一行走到左端点;
上一行在左端点,下一行走到右端点;
上一行在右端点,下一行走到左端点;
上一行在右端点,下一行走到右端点;
我们以"上一行在右端点,下一行走到右端点"为例:
这样我们就可以推出状态转移方程了。
f[i][1]=min(f[i-1][1]+abs(l[i]-r[i-1])+r[i]-l[i]+1,f[i-1][0]+abs(l[i]-l[i-1])+r[i]-l[i]+1);//本行走到右端点,上一行走到左/右端点,取最小值
f[i][0]=min(f[i-1][1]+abs(r[i]-r[i-1])+r[i]-l[i]+1,f[i-1][0]+abs(r[i]-l[i-1])+r[i]-l[i]+1);//本行走到左端点,上一行走到左/右端点,取最小值
最后不要忘了初始化第 (1) 行就可以了。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e4+50;
int n;
int l[maxn],r[maxn];
int f[maxn][2];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
}
f[1][0]=2*r[1]-l[1]-1;//第1行往返,走到右端点再回来
f[1][1]=r[1]-1;//直接走到右端点
for(int i=2;i<=n;i++){
f[i][1]=min(f[i-1][1]+abs(l[i]-r[i-1])+r[i]-l[i]+1,f[i-1][0]+abs(l[i]-l[i-1])+r[i]-l[i]+1);//本行走到右端点,上一行走到左/右端点,取最小值
f[i][0]=min(f[i-1][1]+abs(r[i]-r[i-1])+r[i]-l[i]+1,f[i-1][0]+abs(r[i]-l[i-1])+r[i]-l[i]+1);//本行走到左端点,上一行走到左/右端点,取最小值
}
printf("%d
",min(f[n][1]+n-r[n],f[n][0]+n-l[n]));//因为最后还要走到(n,n)的地方,加上剩下的距离,再加上上下走的距离即可
return 0;
}