1. 程式人生 > >線段樹(區間和,最大值,最小值,區間動態更新查詢)

線段樹(區間和,最大值,最小值,區間動態更新查詢)

//===========================================
//segment tree
//final version
//by kevin_samuel(fenice)
//本模板為轉載模板,後面的註釋和主函式的驗證為Alei新增
#include <iostream>
#include <cstdio>
#include <cmath>
//線段樹


using namespace std;


#define MAXN 100
#define INF 0x3fffffff


int A[MAXN];         //操作的序列,記得為(1...n)非(0...n)
//int max;
//int min;


struct node
{
    int left;
    int right;
    int max;           //維護最大值
    int sum;          //維護區間和
    int min;           //維護最小值
}Tree[MAXN<<2];        //儲存線段樹




void maintain(int root)         //向上調整,使得讓線段樹維護區間最小值最大值區間和
{
    int LC = root<<1;       //此根的左孩子
    int RC = (root<<1)+1;       //此根的右孩子
    Tree[root].sum = Tree[LC].sum + Tree[RC].sum;       //根的區間和
    Tree[root].max = max(Tree[LC].max,Tree[RC].max);        //根的最大值
    Tree[root].min = min(Tree[LC].min,Tree[RC].min);        //根的最小值
}


void Build(int root,int start,int end)                     //構建線段樹
{                                                           //初始化時傳入Build(1,1,n);
    Tree[root].left = start;            //建區間大小
    Tree[root].right = end;
    if(start == end)                    //當到達葉子節點時
    {
        Tree[root].sum = A[start];
        Tree[root].max = A[start];
        Tree[root].min = A[start];
        return;
    }
    int mid = (start + end)>>1;         //中間分開
    Build(root<<1,start,mid);           //對左孩子建樹,左邊孩子的編號為root*2
    Build((root<<1)+1,mid+1,end);       //對右邊孩子建樹
    maintain(root);
}


void update(int root,int pos,int value)                     //更新點的值
{
    if(Tree[root].left == Tree[root].right && Tree[root].left == pos)   //更新葉子節點的值
    {
        Tree[root].sum += value;
        Tree[root].max += value;
        Tree[root].min += value;
        return;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;          //中間分開成兩個區間
    if(pos <= mid)                                       //更新的值在左孩子
        update(root<<1,pos,value);                          //更新左孩子
    else
        update((root<<1)+1,pos,value);                  //更新的值在右孩子
    maintain(root);                                 //葉子節點更新完成後,會回溯到他的父節點,這樣一直往上更新到根節點,維護線段樹性質
}


int Query(int root,int start,int end)                         //查詢區間和(start, end)根節點為1
{
    if(start == Tree[root].left && Tree[root].right == end)         //正好匹配到查詢區間,直接返回區間和
    {
        return Tree[root].sum;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;                 //分開區間
    int ret = 0;
    if(end <= mid)                                              //查詢結果在左邊區間
        ret += Query(root<<1,start,end);                        //將左區間的查詢結果返回,並記錄在結果和中
    else if(start >= mid+1)                                    //查詢結果在右區間
        ret += Query((root<<1)+1,start,end);
    else                                                        //查詢結果包含在左右兩個區間中
    {
        ret += Query(root<<1,start,mid);            //查左的一部分
        ret += Query((root<<1)+1,mid+1,end);            //查右的一部分
    }
    return ret;                             //返回本次查詢結果
}


int RminQ(int root,int start,int end)              //查詢區間最小值
{
    if(start == Tree[root].left && Tree[root].right == end)             //正好匹配區間
    {
        return Tree[root].min;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;                  //區間分開,去查左右孩子
    int ret = INF;                                             //先把結果記錄為很大
    if(end <= mid)                                          //  完全左區間匹配
        ret = min(ret,RminQ(root<<1,start,end));
    else if(start >= mid+1)                                 //完全右區間匹配
        ret = min(ret,RminQ((root<<1)+1,start,end));
    else
    {
        int a = RminQ(root<<1,start,mid);
        int b = RminQ((root<<1)+1,mid+1,end);
        ret = min(a,b);                                     //求左右區間和匹配區間相符的最小值的較小值
    }
    return ret;                             //記得要返回本次查詢的結果
}


int RmaxQ(int root,int start,int end)                 //查詢區間最大值
{
    if(start == Tree[root].left && Tree[root].right == end)
    {
        return Tree[root].max;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;
    int ret = 0;                                        //************可能是 (-INF)要儘可能的小
    if(end <= mid)
        ret = max(ret,RmaxQ(root<<1,start,end));        //完全左孩子區間匹配
    else if(start >= mid+1)
        ret = max(ret,RmaxQ((root<<1)+1,start,end));        //完全右孩子區間匹配
    else
    {
        int a = RmaxQ(root<<1,start,mid);
        int b = RmaxQ((root<<1)+1,mid+1,end);
        ret = max(a,b);                                 //求的左右兩個區間和匹配區間相符的最大值得較大者
    }
    return ret;                             //記得返回結果
}


int main()
{
    for(int i = 1; i <= 10; i++)
        A[i] = i;
    Build(1,1,10);
    cout << " (1..10)的陣列,對應值如下:" << endl;
    for(int i = 1; i <= 10; i++)
        cout << A[i] << " ";
    cout << endl;
    cout << "(1..5)區間最小值:" << RminQ(1,1,5) << endl;
    cout << " (1..5)區間最大值:" << RmaxQ(1,1,5) << endl;
    cout << " (1..5)區間和:" << Query(1,1,5) << endl;
    cout << " 把位置1的值加上100: " << endl;
    update(1, 1, 100);
    cout << " (1..5)區間最小值" << RminQ(1,1,5) << endl;
    cout << " (1..5)區間最大值:" << RmaxQ(1,1,5) << endl;
    cout << " (1..5)區間和:" << Query(1,1,5) << endl;
    return 0;
}
/*
*總結一下線段樹:
*(1)維護區間和,使得區間求和在O(log(n))的複雜度下完成
*(2)維護區間最小值,使得查詢區間最小值在O(log(n))的複雜度下完成
*(3)維護區間最大值
****************************************************************
*(4)更新序列中某個值,也能使得樹維護區間的最大,最小,和區間和。
*(5)基於區間和的應用,最小值得應用,例如序列的動態更新查詢
*/