申請SAE

如果您發現本博客的外觀很難看,那是因為部分外觀文件被中國.國家.防火.牆屏.蔽所致!
請翻~牆!

我的Wordpress博客的地址: http://zhuyf.tk/

2012年1月28日 星期六

[RMQ問題]OI練習題:綿延的山峰 climb 解題報告

Title: 延綿的山峰 傳送門
Input: climb.in
Output: climb.out
Time Limit: 1000 ms
Memory Limit: 128 MB
Level: ★★

問題描述
 
有一座延綿不斷、跌宕起伏的山,最低處海拔為0,最高處海拔不超過8848米,從這座山的一端走到另一端的過程中,每走1米海拔就升高或降低1米。有Q個登山隊計劃在這座山的不同區段登山,當他們攀到各自區段的最高峯時,就會插上隊旗。請你寫一個程序找出他們插旗的高度。


輸入文件
第1行,一個整數N(N<=10^6),表示山兩端的跨度。
接下來N+1行,每行一個非負整數Hi,表示該位置的海拔高度,其中H0=Hn=0。
然後是一個正整數Q(Q<=7000),表示登山隊的數量。
接下來Q行,每行兩個數Ai, Bi,表示第i個登山隊攀爬的區段[Ai,Bi],其中0<=Ai<=Bi<=N。

輸出文件
Q行,每行為一個整數,表示第i個登山隊插旗的高度。

樣例輸入
10
0
1
2
3
2
3
4
3
2
1
0
5
0 10
2 4
3 7
7 9
8 8

樣例輸出
4
3
4
3
2

【分析】
看到題,我首先花了五分鐘敲了一個暴力的代碼,竟然還過8組測試數據!
這題是RMQ問題,也就是區間最值問題。這題一般是用線段樹(又稱“區間樹”) 做的,但是我沒有用線段樹做,用的是ST算法。
ST算法是“分治/二分”的思想,使用了動態規劃。

RMQ(Range Minimum/Maximum Query)問題是求區間最值問題。可以寫一個線段樹,但是預處理和查詢的複雜度都是Ologn)。這裡有更牛的演算法,就是ST演算法,它可以做到O(nlogn)的預處理,O(1)地回答每個詢問。
   
來看一下ST演算法是怎麼實現的(以最大值為例):
       
    首先是預處理,用一個DP解決。設a[i]是要求區間最值的數列,f[i,j]表示從第i個數起連續2^j個數中的最大值。例如數列3 2 4 5 6 8 1 2 9 7 ,f[10]表示第1個數起,長度為2^0=1的最大值,其實就是3這個數。f[12]=5f[13]=8f[20]=2f[21]=4……從這裡可以看出f[i,0]其實就等於a[i]。這樣,Dp的狀態、初值都已經有了,剩下的就是狀態轉移方程。我們把f[ij]平均分成兩段(因為f[ij]一定是偶數個數字),從ii+2^(j-1)-1為一段,i+2^(j-1)i+2^j-1為一段(長度都為2^j-1)。用上例說明,當i=1j=3時就是3,2,4,5 6,8,1,2這兩段。f[ij]就是這兩段的最大值中的最大值。於是我們得到了動規方程F[i,j]=maxF[ij-1],F[i+2^(j-i)j-1].
     
    接下來是得出最值,也許你想不到計算出f[ij]有什麼用處,一般毛想想計算max還是要O(logn),甚至O(n)。但有一個很好的辦法,做到了O1)。還是分開來。如在上例中我們要求區間[28]的最大值,就要把它分成[2,5][5,8]兩個區間,因為這兩個區間的最大值我們可以直接由f[22]f[52]得到。擴展到一般情況,就是把區間[lr]分成兩個長度為2^n的區間(保證有f[ij]對應)。直接給出運算式:
 f[i,j] 表示 從第 i 個數數 2^j 中最小的數。那麼:
        最大值 f[i,j]=max(f[i,j-1],f[i+2^(j-1),j-1]);
        最小值 f[i,j]=min(f[i,j-1],f[i+2^(j-1),j-1]);



【我的代碼】
C++语言: Codee#25395
01 /*
02 *Problem: 延綿的山峰 climb
03 *Author: Yee-fan Zhu
04 *Method: ST-Algoritm/Dynamic Programming
05 *GTalk: zyfworks@gmail.com
06 *Website:http://yeefanzhu.blogspot.com/
07 */
08 #include <cstdio>
09 #include <iostream>
10 #include <cstdlib>
11 #include <cmath>
12 using namespace std;
13
14 int MAX(int a,int b)
15 {
16     return a>b?a:b;
17 }
18
19 const int MAXN=1000003;
20 const int MAX_INDEX=10;
21 int N,Q;
22 int Max[MAXN][MAX_INDEX];
23 int Num[MAXN];
24
25 void init()
26 {
27     scanf("%d\n",&N);
28     N++;
29     for (int i=1;i<=N;i++)
30         scanf("%d\n",&Num[i]),
31         Max[i][0]=Num[i];
32     scanf("%d\n",&Q);
33    
34     int M=int(floor(log((double)N)/log(2.0)));
35     for (int i=1;i<=M;i++)
36     {
37         for (int j=N;j>=1;j--)
38         {
39             Max[j][i]=Max[j][i-1];
40             if(j+(1<<(i-1))<=N )
41                 Max[j][i]=MAX(Max[j][i],Max[j+(1<<(i-1))][i-1]);
42         }
43     }
44 }
45
46 int RMQ(int l,int r)
47 {
48     int m=int (floor(log((double)(r-l+1))/log(2.0)));
49       int a=MAX(Max[l][m],Max[r-(1<<m)+1][m]);
50         return a;  
51 }
52
53 int main()
54 {
55     freopen("climb.in","r",stdin);
56     freopen("climb.out","w",stdout);
57     init();
58     int a,b;
59     for (int i=1;i<=Q;i++)
60     {
61         scanf("%d %d\n",&a,&b);
62         a++,b++;
63         printf("%d\n",RMQ(a,b));
64     }
65     return 0;
66 }

沒有留言:

張貼留言