题解 P3842 【[TJOI2007]线段】

ShineEternal

2020-04-15 21:04:54

Solution

[更护眼的阅读效果。](https://blog.csdn.net/kkkksc03/article/details/105544432) ## description 在一个 $n\times n$ 矩阵中每一行都有一条线段,找出从 $(1,1)$ 出发不重不漏地遍历每一条线段并走到 $(n,n)$ 结束所需的步数。 ## solution **使用 dp。** 考虑设 $f[i][0/1]$ 表示当前以及遍历完了第 $i$ 行的线段,在左($0$)/ 右($1$)端点结束遍历(即准备从这个端点跳到下一行)。 **那么我们可以考虑两行之间衔接的四种情况:** - 左端点到左端点:$f[i][0]=f[i-1][0]+ abs(L[i]-L[i-1])+R[i]-L[i]+1$,其中 $abs(L[i]-L[i-1])$ 为两个左端点之间列数的差距,$R[i]-L[i]$ 为遍历一个线段的步数,而因为向下了一行所以要 $+1$。 - 右端点到左端点:$f[i][0]=f[i-1][1]+ abs(L[i]-R[i-1])+R[i]-L[i]+1$,这个时候同理,只是变换了第 $i-1$ 行的下行节点和转移来的 $f$ 的值。 - 左端点到右端点:$f[i][1]=f[i-1][0]+ abs(R[i]-L[i-1])+R[i]-L[i]+1$。 - 右端点到右端点:$f[i][1]=f[i-1][1]+ abs(R[i]-R[i-1])+R[i]-L[i]+1$。 那么对于 $f[i][0]$ 和 $f[i][1]$ ,我们选择上一行的最优端点转移下来就行了。 **初始化:** 如果刚开始到左端点的话要走两遍线段长,如果右端点可以直接一次性走过去。 **输出:** 由于 $f$ 的值是以线段端点为结束的,而题目要求以 $(n,n)$ 为终点,所以要走到 $n$。 ## code ```cpp #include<cstdio> using namespace std; int f[20005][3];//设f[i][0]表示走完了第i行且走到左端点;设f[i][1]表示走完了第i行且走到了右端点。 int abs(int x) { if(x<0)return -x; return x; } int min(int x,int y) { if(x<y)return x; return y; } int L[20005],R[20005]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d%d",&L[i],&R[i]); } f[1][0]=L[1]-1+(R[1]-L[1])*2; f[1][1]=R[1]-1; for(int i=2;i<=n;i++) { f[i][0]=min(f[i-1][1]+abs(R[i-1]-R[i]),f[i-1][0]+abs(L[i-1]-R[i]))+R[i]-L[i]+1; f[i][1]=min(f[i-1][1]+abs(R[i-1]-L[i]),f[i-1][0]+abs(L[i]-L[i-1]))+R[i]-L[i]+1; } printf("%d\n",min(f[n][0]+n-L[n],f[n][1]+n-R[n])); return 0; } ```