1. 程式人生 > >線段樹入門(建樹,查詢,更新)hdu1754

線段樹入門(建樹,查詢,更新)hdu1754

先來一道純線段樹的題目:

I Hate It

Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 75617    Accepted Submission(s): 29141


Problem Description 很多學校流行一種比較的習慣。老師們很喜歡詢問,從某某到某某當中,分數最高的是多少。
這讓很多學生很反感。

不管你喜不喜歡,現在需要你做的是,就是按照老師的要求,寫一個程式,模擬老師的詢問。當然,老師有時候需要更新某位同學的成績。
Input 本題目包含多組測試,請處理到檔案結束。
在每個測試的第一行,有兩個正整數 N 和 M ( 0<N<=200000,0<M<5000 ),分別代表學生的數目和操作的數目。
學生ID編號分別從1編到N。
第二行包含N個整數,代表這N個學生的初始成績,其中第i個數代表ID為i的學生的成績。
接下來有M行。每一行有一個字元 C (只取'Q'或'U') ,和兩個正整數A,B。
當C為'Q'的時候,表示這是一條詢問操作,它詢問ID從A到B(包括A,B)的學生當中,成績最高的是多少。
當C為'U'的時候,表示這是一條更新操作,要求把ID為A的學生的成績更改為B。

Output 對於每一次詢問操作,在一行裡面輸出最高成績。
Sample Input 5 6 1 2 3 4 5 Q 1 5 U 3 6 Q 3 4 Q 4 5 U 2 9 Q 1 5
Sample Output 5 6 5 9 Hint
Huge input,the C function scanf() will work better than cin 一開始沒有學線段樹怎麼辦? 妥妥的來一發暴力,程式碼如下:
#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 5;
const int min1 = -1e9;
int main()
{
    int N, M;
    int score[maxn];
    while (scanf("%d%d",&N,&M)!=EOF){
        for (int i=0; i<N; i++){
            scanf("%d",&score[i]);
        }
        char query;
        int a, b;
        for (int k=0; k<M; k++){
            getchar();//媽的字元輸入有毒,好不容易才知道原因~~
            scanf("%c%d%d",&query, &a, &b);
            int ans = min1;
            if (query == 'Q'){
                for (int j = a-1; j<=b-1;j++){
                    if(score[j]>ans){
                        ans = score[j];
                    }
                }
                printf("%d\n",ans);
            }
            else{
                score[a-1] = b;
            }
        }

    }
    return 0;
}
結果可想而知,必定是TLE; 好了那麼線段樹有什麼優點呢? 他的查詢的效率高,從O(n)降到了O(logn)的複雜度,AC程式碼如下:
#include <bits/stdc++.h>
using namespace std;

const int MAX_NODE = 1<<19;
const int maxn = 2e6+5;

struct Node{
    int value;
    int left, right;
}node[MAX_NODE];

int father[maxn];

void buildtree(int i, int l, int r){

    node[i].left = l;
    node[i].right = r;
    node[i].value = 0;
    if (l == r){//這一步要放在下面寫,千萬不要放在上面,否則會報錯~~~被坑了好長時間
        father[l] = i;
        return;
    }
    buildtree(i*2, l, (int)(floor(l+r)/2.0));
    buildtree(i*2+1, (int)(floor(l+r)/2.0)+1, r);
}


void update_tree(int children){
    if (children == 1) return;
    int father = children/2;
    int a = node[father*2].value;
    int b = node[father*2+1].value;
    node[father].value = max(a, b);
    update_tree(father);
}

int MAX_NUMBER;

void query(int i, int l, int r){
    if (node[i].left == l && node[i].right == r) {
        MAX_NUMBER = max(node[i].value, MAX_NUMBER);
        return;
    }
    i = i*2;//這裡一開始移位有問題QAQ~~
    if (l<=node[i].right){
        if (r<=node[i].right)
            query(i, l, r);
        else
            query(i, l, node[i].right);
    }
    i++;
    if (r >= node[i].left){
        if (l >= node[i].left)
            query(i, l, r);
        else
            query(i, node[i].left, r);
    }
    return ;
}

int main()
{
    int n, m, g;
    ios::sync_with_stdio(false);
    while(cin>>n>>m){
        buildtree(1, 1, n);
//        for (int i=1; i<=9; i++){
//            cout<<father[i]<<" ";
//        }
        for (int i=1; i<=n; i++){
            cin>>g;
            node[father[i]].value = g;
            update_tree(father[i]);
        }
        string op;
        int a, b;
        while (m--){
            cin>>op>>a>>b;
            if (op[0]=='Q'){
                MAX_NUMBER = -10000;
                //cout<<op<<" "<<a<<" "<<b<<endl;
                query(1, a, b);
                cout<<MAX_NUMBER<<endl;
            }
            else{
                node[father[a]].value = b;
                update_tree(father[a]);
            }
        }
    }
    return 0;
}
這裡說一下大概的思路,首先,不用知道每個點的權值就可以先,建立一棵樹,至於father[]陣列的用處,可以打印出來看到它的作用是記錄單個數組成的區間的編號: 當n = 5時: father[1] = 8, father[2] = 9,依次類推:5, 6, 7; 這麼經典的題目,乖乖滾回去再敲一遍(逃