1. 程式人生 > 實用技巧 >#洛谷 P3842 [TJOI2007]線段

#洛谷 P3842 [TJOI2007]線段

P3842 [TJOI2007]線段


思路

1.出發點

顯然顯然顯然(重要的事情說三遍),這一行的終點可能是線段的左邊也可能是線段的右邊,所以要想走完這一行的線段,就需要從上一行的左端點或右端點,進而就有了下面的討論

2.討論

計f[0][i]為走完第i行的線段且

情況1:上一行線段在左,下一行線段在右

此時分兩種圖:


很顯然,這兩種可能可以合併

第一種圖:

圖1:

這裡指出一個誤區:明明線路\(2\)比線路\(1\)要優秀,為什麼還要考慮呢?其實很簡單,\(f[0][i-1]\)可能要比\(f[1][i-1]\)小很多,而不是\(f[1][i-1]\)一定大於\(f[0][i-1]\),所以\(f[0][i-1]+abs(r[i]-l[i-1])\)

(從上一行左端點到這一行右端點)可能小於\(f[1][i-1]+abs(r[i-1]-r[i])\)(從上一行右端點到這一行右端點)

圖2:

狀態轉移方程為:

  f[0][i]=min(f[0][i-1]+abs(r[i]-l[i-1]),f[1][i-1]+abs(r[i-1]-r[i]))+len[i]+1;
  f[1][i]=min(f[0][i-1]+abs(l[i-1]-l[i]),f[1][i-1]+abs(r[i-1]-l[i]))+len[i]+1;

第二種圖:

圖1:

圖2:

狀態轉移方程為:

  f[0][i]=min(f[0][i-1]+abs(r[i]-l[i-1]),f[1][i-1]+abs(r[i-1]-r[i]))+len[i]+1;
  f[1][i]=min(f[0][i-1]+abs(l[i-1]-l[i]),f[1][i-1]+abs(r[i-1]-l[i]))+len[i]+1;

驚奇的發現,狀態轉移方程竟然一樣,這樣就減少判斷縮減了程式碼

情況2:上一行線段在中,下一行線段在中

此時也分兩種圖:


顯然這兩種也可以合併

同樣四張圖:




狀態轉移方程:

  f[0][i]=min(f[0][i-1]+abs(r[i]-l[i-1]),f[1][i-1]+abs(r[i-1]-r[i]))+len[i]+1;
  f[1][i]=min(f[0][i-1]+abs(l[i-1]-l[i]),f[1][i-1]+abs(r[i-1]-l[i]))+len[i]+1;

我們又要口矣了,兩種大情況的狀態轉移方程也一樣誒,顯然第三種情況(上一行線段在右,下一行線段在左)也是這個狀態轉移方程,都可以合併就不用判斷了誒誒誒(當然可以自己在畫畫第三種情況的圖)

程式碼:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=2e4+5,INF=0x3f3f3f3f,mol=1000007;
int n,m,f[2][maxn],a[maxn],l[maxn],r[maxn],len[maxn];
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int main(){
	//freopen("a.in","r",stdin);
	n=read();
	for(int i=1;i<=n;i++)l[i]=read(),r[i]=read(),len[i]=r[i]-l[i];
	f[0][1]=r[1]-1+len[1];f[1][1]=r[1]-1;
	for(int i=2;i<=n;i++){
		f[0][i]=min(f[0][i-1]+abs(r[i]-l[i-1]),f[1][i-1]+abs(r[i-1]-r[i]))+len[i]+1;
		f[1][i]=min(f[0][i-1]+abs(l[i-1]-l[i]),f[1][i-1]+abs(r[i-1]-l[i]))+len[i]+1;
	}
	cout<<min(f[0][n]+n-l[n],f[1][n]+n-r[n]);
	
}

OVER~