1. 程式人生 > >線段樹:CDOJ1591-An easy problem A (RMQ演算法和最簡單的線段樹模板)

線段樹:CDOJ1591-An easy problem A (RMQ演算法和最簡單的線段樹模板)

An easy problem A

Time Limit: 1000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others)

Problem Description

N個數排成一列,Q個詢問,每次詢問一段區間內的數的極差是多少。

Input

第一行兩個整數N(1≤N≤50000),Q(1≤Q≤200000)。接下來一行N個整數a1 a2 a3 ….an,(1≤ai≤1000000000)。接下來Q行,每行兩個整數L,R(1≤L≤R≤N)。

Output

對於每個詢問輸出一行,一個整數表示區間內的極差。

Sample Input

5 3
3 2 7 9 10
1 5
2 3
3 5

Sample Output

8
5
3

解題心得:

一、線段樹
1. 這是一個最簡單的線段樹的模板,主要是入門理解線段樹。當資料量比較大,需要多次詢問,多次修改維護資料時,就需要使用到線段樹的資料結構。線段樹的每個節點代表的是一段線段,每次修改這個線段,就可以由此對每個區間進行維護,縮減時間複雜度和空間複雜度。
2. 一般線段樹的空間複雜度是O(4n),建立線段樹時間複雜度是O(logn),向上更新的時間複雜度是O(logn),詢問的時間複雜度是O(logn),如果不使用線段樹就遍歷硬懟時間複雜度是O(n^2)。線段樹是根節點將根節點代表的線段均分為兩個線段,分別是左子節點和右子節點,然後一直分下去直到一個線段的長度為1(只有一個數)對該點進行初始賦值,然後向上維護得到每一個線段的需要得到值,線段樹就是儘量避免對單個的點進行操作,而是對區間進行維護,這個就避免一個點一個點的去比較,可以直接從區間上面進行操作,樹形結構可以很方便的進行維護操作。
3. 就本題來說就是將整個數列按照完美二叉樹的樣子直接分下去,一直分到區間長度為1,這時這個節點的最大值和最小值都是他自己,然後再向上更新,向上更新的時候父節點的最大值和最小值又他的兩個子節點得到,一直向上更新,時間複雜度是nlogn。

二、

  1. 這個還可以使用RMQ演算法,即區間最值查詢演算法,這個演算法就是在區間中使用動態規劃對這個區間進行預處理,倍增思想。
  2. 動態規劃時建立一個dp[i][j],代表以I為起點長度為2^j次方的區間最值,它的轉移方程式是dp[I][j] = max(dp[i][j-1],dp[i+2^(j-1)][j-1]),它的意思是從i開始長度為2^j區間的最大值和i+2^(j-1)和長度為2^(j-1)區間的最大值(倍增演算法的基礎運用)。
  3. 當要求在I到j的最大值的時候可以用i到i + 2 ^ x(i+ 2 ^ x < j ) 和 j - 2 ^ x到j中的最大值。(MAX = max(max(num[i],num[I+2^x]),max(num[j-2^x],num[j]))).
  4. 其實仔細想想,線段數和RMQ的演算法是相似的,只是實現的方法不一樣,但是思想是一樣的(一個二分,一個倍增)。都是將點聯絡起來用區間來表示。
線段樹程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4+100;
struct node
{
    int l,r,Max,Min;
}tree[4*maxn];//經計算生成線段樹消耗的空間小於4n
int n,m,num[maxn];

//向上更新
void pushup(int root)
{
    tree[root].Max = max(tree[root*2].Max,tree[root*2+1].Max);
    tree[root].Min = min(tree[root*2].Min,tree[root*2+1].Min);
}

//生成線段樹
void build_tree(int l,int r,int root)
{
    int mid;
    tree[root].l = l;
    tree[root].r = r;
    if(l == r)
    {
        tree[root].Min = tree[root].Max = num[l];
        return ;
    }
    mid = (l+r)/2;
    build_tree(l,mid,root*2);//左孩子
    build_tree(mid+1,r,root*2+1);//右孩子
    pushup(root);//線上段樹已經建成了之後開始向上更新
}

//得到最大值
int queryMax(int l,int r,int root)
{
    int mid;
    if(tree[root].l == l && tree[root].r == r)
        return tree[root].Max;
    mid = (tree[root].l + tree[root].r) / 2;
    if(r <= mid)
        return queryMax(l,r,root*2);
    else if(l > mid)
        return queryMax(l,r,root*2+1);
    else return max(queryMax(l,mid,root*2),queryMax(mid+1,r,root*2+1));
}

//得到最小值
int queryMin(int l,int r,int root)
{
    int mid;
    if(tree[root].l == l && tree[root].r == r)
        return tree[root].Min;
    mid = (tree[root].l + tree[root].r) / 2;
    if(r <= mid)
        return queryMin(l,r,root*2);
    else if(l > mid)
        return queryMin(l,r,root*2+1);
    else return min(queryMin(l,mid,root*2),queryMin(mid+1,r,root*2+1));
}

int main()
{
    while(scanf("%d%d",&n,&m) != EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&num[i]);
        build_tree(1,n,1);
        while(m--)
        {
            int a,b;
            scanf("%d%d",&a,&b);
        `
   printf("%d\n",queryMax(a,b,1)-queryMin(a,b,1));
        }
    }
}

RMQ演算法

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4+100;
int num[maxn];
int n,m;

struct Dp
{
    int Min;
    int Max;
} dp[maxn][20];

void DP()
{
    for(int j=0; j<20; j++)
    {
        for(int i=1; i<=n; i++)
        {
            if(j == 0)
            {
                dp[i][j].Max = dp[i][j].Min = num[i];//當就只有一個數的時候最大值和最小值都是這個數
                continue;
            }
            if(i + (1<<(j - 1)) > n)//不能超出限制
                continue;
            int k = 1<<(j-1);
            dp[i][j].Max = max(dp[i][j-1].Max,dp[i+k][j-1].Max);//狀態轉移方程式
            dp[i][j].Min = min(dp[i][j-1].Min,dp[i+k][j-1].Min);
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m) != EOF)
    {
        memset(dp,0,sizeof(dp));

        for(int i=1; i<=n; i++)
            scanf("%d",&num[i]);
        DP();
        while(m--)
        {
            int l,r,l1,r1;
            scanf("%d%d",&l,&r);
            //這是將要求的區間化成兩重合的可以用dp[i][j]的區間來表示,求最大值
            r1 = log2(r-l+1);
            l1 = r - (1<<r1) + 1;
            printf("%d\n",max(dp[l][r1].Max,dp[l1][r1].Max)-min(dp[l][r1].Min,dp[l1][r1].Min));
        }
    }
    return 0;
}