1. 程式人生 > >poj 2566"Bound Found"(尺取法)

poj 2566"Bound Found"(尺取法)

傳送門

 

參考資料:

  [1]:http://www.voidcn.com/article/p-huucvank-dv.html

題意:

  題意就是找一個連續的子區間,使它的和的絕對值最接近target。

題解:

  這題的做法是先預處理出字首和,然後對字首和進行排序,再用尺取法貪心的去找最合適的區間。

  要注意的是尺取法時首尾指標一定不能相同,因為這時區間相減結果為0,但實際上區間為空,這是不存在的,可能會產生錯誤的結果。

  處理時,把(0,0)這個點也放進陣列一起排序,比單獨判斷起點為1的區間更方便。

  還有ans初始化的值INF一定要大於t的最大值。

  最後說說這個題最重要的突破口,對字首和排序。為什麼這麼做是對的呢?

  因為這題是取區間的和的絕對值,所以所以用sum[r]-sum[l] 和 sum[l]-sum[r]是沒有區別的。

  這樣排序後,把原來無序的字首和變成有序的了,就便於列舉的處理,並且不影響最終結果。

  以上分析來自參考資料[1]。

AC程式碼:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 #define P pair<int ,int >
 6 const int maxn=1e5+10
; 7 8 int n,k; 9 P p[maxn]; 10 11 bool cmp(P _a,P _b){ 12 return _a.second < _b.second; 13 } 14 void Solve(int t) 15 { 16 int l=0,r=1; 17 int resL=p[l].first,resR=p[r].first;//先假設區間[1,p[1].first]為解 18 int resSum=p[r].second-p[l].second; 19 while(r <= n) 20 { 21 int
curSum=p[r].second-p[l].second; 22 if(abs(curSum-t) < abs(resSum-t))//判斷是否可以更新 resSum 23 { 24 resSum=curSum; 25 resL=p[l].first; 26 resR=p[r].first; 27 } 28 if(curSum < t)//如果當前區間值過小,增大當前值 29 r++; 30 else if(curSum > t)//如果當前區間值過大,減小當前值 31 l++; 32 else 33 break; 34 if(l == r) 35 r++; 36 } 37 if(resL > resR) 38 swap(resL,resR); 39 printf("%d %d %d\n",resSum,resL+1,resR);//while()迴圈中做區間減法時始終左邊界一直被減掉 40 } 41 int main() 42 { 43 // freopen("C:\\Users\\lenovo\\Desktop\\in.txt\\poj2566.txt","r",stdin); 44 while(~scanf("%d%d",&n,&k),n != 0 || k != 0) 45 { 46 int sum=0; 47 for(int i=1;i <= n;++i) 48 { 49 int val; 50 scanf("%d",&val); 51 sum += val; 52 p[i]=P(i,sum); 53 } 54 p[0]=P(0,0); 55 sort(p,p+n+1,cmp); 56 while(k--) 57 { 58 int t; 59 scanf("%d",&t); 60 Solve(t); 61 } 62 } 63 return 0; 64 }
View Code

我的理解:

  為什麼對字首和用尺取法是正確的呢?

  定義 pair<int ,int > 型變數 p[ maxn ];

  p[ i ].first : 右邊界;

  p[ i ].second : 前 p[ i ].first 項和;

  將 p 按字首和從小到大排序後,如圖所示:

  

  縱座標 : 排序後的字首和

  假設 ( p[b].second-p[a].second ) 距 t 最近 , resSum = p[b].second-p[a].second;

  問題1:當 R 來到區間(a,b)時,L 有可能超過 a 嗎?

  答案:不會。

  分析如下:當 a < R < b , L = a 時, 令 curSum=p[R].second-p[L].second;

  易得 curSum < resSum;

  根據 Solve() 中 while() 迴圈的程式碼,只由當 curSum > target 時,才會使 L++;

  假設 curSum > target , 則 resSum > curSum > target,那麼答案就為 curSum 所標示的區間而不是resSum 所表示的區間,與假設不符;
  故當 L = a , a < R < b 時,curSum < target ,直到 R 來到 b 。

  問題2:當 L < a 時,R 有可能超過 b 嗎?

  答案:不會。

  分析如下:當 L < a , R = b 時,令 curSum=p[R].second-p[L].second;

  易得 curSum > resSum;

  根據 Solve() 中 while() 迴圈的程式碼,只由當 curSum < target 時,才會使R++;

  假設 curSum < target , 則 resSum < curSum < target,那麼答案就為 curSum 所標示的區間而不是resSum 所表示的區間,與假設不符;

  故當 R = b , L < a 時,curSum > target ,直到 L 來到 a 。

  綜上所述,L,R一定會來到答案所對應的區間。

  問題3:為什麼要加入 (0,0)點?

  根據Solve()中的while()迴圈可知,curSum求的是L,R區間的差集(大-小)的和,如果答案的左區間為 1 呢?

  不加入 (0,0) 點就永遠也不可能使某兩區間的差集(大-小)包含 1 .

  或者說可以另用一重迴圈判斷,感覺加入 (0,0)點更美觀,哈哈哈。