線段樹總結(不看後悔系列)

普通線段樹

線段樹是算法競賽中常用的用來維護 區間信息 的數據結構。

線段樹可以在 O(logN)O(log N) 的時間複雜度內實現單點修改、區間修改、區間查詢(區間求和,求區間最大值,求區間最小值)等操作。(注意:查詢時一定要注意 查詢的範圍不能超過所建立線段樹區間的範圍)

線段樹維護的信息,需要滿足區間可加性,即能以可以接受的速度合併信息和修改信息,包括在使用懶惰標記時,標記也要滿足可加性(例如取模就不滿足可加性,對44 取模然後對 33 取模,兩個操作就不能合併在一起做)。看下圖理解下(圖片來自網絡
圖片來自網絡

模板

節點 數據向上跟新
將子節點的值更新到父節點。

//對於區間求和
inline void push_up(int rt){
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
/* 對於區間求最大值 */
void push_up(int rt) {
    tree[rt] = max(tree[rt << 1], tree[rt << 1 | 1]);
}

節點懶惰標記下推
對於區間求和, 原子數組值需要加上lazy標記乘以子樹所統計的區間長度。 len爲父節點統計的區間長度, 則len - (len >> 1)爲左子樹區間長度, len >> 1爲右子樹區間長度。
懶標記的含義是:該節點曾經被修改,但其子節點尚未被更新

inline void push_down(int rt,int len){//len = r - l +1
    tree[rt<<1] += lzy[rt]*(len - (len>>1));
    lzy[rt<<1] += lzy[rt];
    tree[rt<<1|1] += lzy[rt]*(len>>1);
    lzy[rt<<1|1] += lzy[rt];
    lzy[rt] = 0;
}

對於區間求最大值, 子樹的值不需要乘以長度, 所以不需要傳遞參數len。

void push_down(int rt) {
    tree[rt << 1] += lazy[rt];
    lazy[rt << 1] += lazy[rt];
    tree[rt << 1 | 1] += lazy[rt];
    lazy[rt << 1 | 1] += lazy[rt];
    lazy[rt] = 0;
}

建樹
新建一棵長度N的線段樹。

void built(int rt,int l,int r){
    if(l == r){
        tree[rt] = read();
        return ;
    }
    int mid = l + r >> 1;
    built(rt<<1,l,mid);
    built(rt<<1|1,mid+1,r);
    push_up(rt);
}

更新
單點更新, 不需要用到lazy標記

void update(int p,int dat,int rt,int l,int r){
    if(l == r) {
        tree[rt] += dat;
        return ;
    }
    int mid = l + r >> 1;
    if(p <= m) update(p,dat,rt<<1,l,mid);
    else update(p,dat,rt<<1|1,mid+1,r);
    push_up(rt);
}

成段更新, 需要用到lazy標記來提高時間效率

void update(int L,int R,int dat,int rt,int l,int r){
    if(L<=l&&r<=R){
        tree[rt] += 1LL*(r - l + 1)*dat;
        lzy[rt] += dat;
        return ;
    }
    if(lzy[rt]) push_down(rt,r - l + 1);
    int mid = l + r >> 1;
    if(L <= mid) update(L,R,dat,rt<<1,l,mid);
    if(R > mid) update(L,R,dat,rt<<1|1,mid+1,r);
    push_up(rt);
}

查詢
單點查詢

int query(int p,int rt,int l,int r){
    if(l == r) return tree[rt];
    int mid = l + r >> 1;
    if(p <= mid) return query(p,rt<<1,l,mid);
    else return query(p,rt<<1|1,mid+1,r);
}

區間查詢

ll query(int L,int R,int rt,int l,int r){
    if(L <= l&& r<= R){
        return tree[rt];
    }
    if(lzy[rt]) push_down(rt,r - l + 1);
    int mid = l + r >> 1;
    ll ans = 0;
    if(L <= mid) ans += query(L,R,rt<<1,l,mid);
    if(R > mid) ans += query(L,R,rt<<1|1,mid+1,r);
    return ans;
}

一些例題
hdu1166 敵兵佈陣
單點修改,區間查詢

const int M = 50000+5;
int tree[M<<2];
void built(int rt,int l,int r){
    if(l == r){
        tree[rt] = read();return ;
    }
    int mid = l + r >> 1;
    built(rt<<1,l,mid);
    built(rt<<1|1,mid+1,r);
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
void update(int p,int data,int rt,int l,int r){
    if(l == r) {
        tree[rt] += data;//注意:是增加或者減少,不是替換!
        return ;
    }
    int mid = l + r>>1;
    if(p<=mid) update(p,data,rt<<1,l,mid);
    else update(p,data,rt<<1|1,mid+1,r);
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
int query(int L,int R,int rt,int l,int r){
    if(L<=l&&r<=R) return tree[rt];
    int mid = l + r >> 1;
    int ans = 0;
    if(L<=mid) ans += query(L,R,rt<<1,l,mid);
    if(R>mid) ans += query(L,R,rt<<1|1,mid+1,r);
    return ans;
}
int main(){
    int t = read();
    rep(p,1,t){
        int n = read();
        built(1,1,n);
        printf("Case %d:\n",p);
        char op[7];
        while(scanf("%s",op)!=EOF&&op[0]!='E'){
            if(op[0] == 'Q'){
                int l = read(),r = read();
                printf("%d\n",query(l,r,1,1,n));
            }
            else {
                int x = read(),y = read();
                if(op[0] == 'A') update(x,y,1,1,n);
                else update(x,-y,1,1,n);
            }
        }
    }
}

Acwing243. 一個簡單的整數問題2
區間修改,區間查詢
在這裏插入圖片描述

ll tree[N<<2],lzy[N<<2];
inline void push_up(int rt){
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
inline void push_down(int rt,int len){
    tree[rt<<1] += lzy[rt]*(len - (len>>1));
    lzy[rt<<1] += lzy[rt];
    tree[rt<<1|1] += lzy[rt]*(len>>1);
    lzy[rt<<1|1] += lzy[rt];
    lzy[rt] = 0;
}
void built(int rt,int l,int r){
    if(l == r){
        tree[rt] = read();
        return ;
    }
    int mid = l + r >> 1;
    built(rt<<1,l,mid);
    built(rt<<1|1,mid+1,r);
    push_up(rt);
}
void update(int L,int R,int dat,int rt,int l,int r){
    if(L<=l&&r<=R){
        tree[rt] += 1LL*(r - l + 1)*dat;
        lzy[rt] += dat;
        return ;
    }
    if(lzy[rt]) push_down(rt,r - l + 1);
    int mid = l + r >> 1;
    if(L <= mid) update(L,R,dat,rt<<1,l,mid);
    if(R > mid) update(L,R,dat,rt<<1|1,mid+1,r);
    push_up(rt);
}
ll query(int L,int R,int rt,int l,int r){
    if(L <= l&& r<= R){
        return tree[rt];
    }
    if(lzy[rt]) push_down(rt,r - l + 1);
    int mid = l + r >> 1;
    ll ans = 0;
    if(L <= mid) ans += query(L,R,rt<<1,l,mid);
    if(R > mid) ans += query(L,R,rt<<1|1,mid+1,r);
    return ans;
}
int main(){
    int n = read(),m = read();
    built(1,1,n);
    while(m--){
        char q = gc();
        if(q == 'Q'){
            int l = read(),r = read();
            if(l > r) swap(l,r);
            printf("%lld\n",query(l,r,1,1,n));
        }
        else {
            int l = read(),r = read(),d=  read();
            update(l,r,d,1,1,n);
        }
    }
}

P3373 【模板】線段樹 2
區間修改,區間查詢

在這裏插入圖片描述
思路:

tmd,*******************************,sb取模卡我。。。。

這個題目維護兩個lazylazy 標記,加法標記和乘法標記。

然後區間更新時,記住乘法優先的原則。

sum(l,r) * x1 + x2 乘以個 k 等價於 sum(l,r)* x1 * k + x2*k

sum(l,r) * x1 + x2 加上一個 k 等價於sum(l,r) * x1 + (x2 + k)

ll tree[N<<2],add[N<<2],mul[N<<2];
inline void push_up(int rt){
    tree[rt] = (tree[rt<<1] + tree[rt<<1|1]) % M;
}
inline void push_down(int rt,int len){
    tree[rt<<1] = (tree[rt<<1]*mul[rt]%M + 1LL*(len-(len>>1))*add[rt]%M)%M;
    tree[rt<<1|1] = (tree[rt<<1|1]*mul[rt]%M + 1LL*(len>>1)*add[rt]%M)%M;

    mul[rt<<1] = (mul[rt<<1]*mul[rt])%M;
    mul[rt<<1|1] = (mul[rt<<1|1]*mul[rt])%M;

    add[rt<<1] = (add[rt<<1]*mul[rt]%M + add[rt])%M;
    add[rt<<1|1] = (add[rt<<1|1]*mul[rt]%M+ add[rt])%M;

    add[rt] = 0;mul[rt] = 1;
}
void built(int rt,int l,int r){
    add[rt] = 0;mul[rt] = 1;
    if(l == r) {tree[rt] = read()%M;return ;}
    int mid = l + r >> 1;
    built(rt<<1,l,mid);
    built(rt<<1|1,mid+1,r);
    push_up(rt);
}
void update(int L,int R,ll k,int flag,int rt,int l,int r){
    if(L <= l && r <= R){
        if(flag ==1 ){//乘法運算
            tree[rt] = (tree[rt]*k)%M;
            mul[rt] = (mul[rt]*k)%M;
            add[rt] = (add[rt]*k)%M;
        }
        else {//加法運算
            tree[rt] = (tree[rt] + 1LL*(r-l+1)*k%M)%M;
            add[rt] = (add[rt] + k)%M;
        }
        return ;
    }
    if(add[rt]||mul[rt]!=1) push_down(rt,r-l+1);
    int mid = l + r >> 1;
    if(L <= mid) update(L,R,k,flag,rt<<1,l,mid);
    if(R > mid) update(L,R,k,flag,rt<<1|1,mid+1,r);
    push_up(rt);
}
ll query(int L,int R,int rt,int l,int r){
    if(L <= l&&r <= R) return tree[rt];
    if(add[rt]||mul[rt]!=1) push_down(rt,r-l+1);
    int mid = l + r >> 1;
    ll ans = 0;
    if(L <= mid) ans = (ans + query(L,R,rt<<1,l,mid))%M;
    if(R > mid) ans = (ans + query(L,R,rt<<1|1,mid+1,r)) % M;
    return ans;
}
int main(){
    int n = read(),m = read(),p = read();
    M = p;
    built(1,1,n);
    while(m--){
        int op = read(),l =read(),r = read();
        if(op == 1){
            ll k = read();
            update(l,r,k,1,1,1,n);
        }
        else if(op == 2){
            ll k = read();
            update(l,r,k,2,1,1,n);
        }
        else cout<<query(l,r,1,1,n)<<endl;
    }
}

GSS5 - Can you answer these queries V

在這裏插入圖片描述

思路:

這個題目和上一個題目的區別在於,這個題目給定區間端點是在一個範圍內,然後我們可以進行分類討論。

1、這兩個區間是相離的,那麼這個最後查詢的區間,肯定會包括[r1+1,l21][r_1+1,l_2-1] ,所以我們要求的答案爲rmax(l1,r1)+Sum(r1+1,l21)+lmax(l2,r2)rmax(l_1,r_1)+Sum(r_1+1,l_2-1)+lmax(l_2,r_2)

2、這兩個區間是相交的(r1=l2r_1=l_2),

​ 1)如果這兩個區間重合,那麼答案是ms(l1,r1)ms(l_1,r_1)

​ 2)不重合的情況,答案爲rmax(l1,r1)+lmax(l2,r2)a[r1]rmax(l_1,r_1)+lmax(l2,r2)-a[r_1] ,因爲我們多算了一次r1r_1 ,所以要減去

3、相交的情況,有點複雜

​ 1)如果兩個端點在相交的區域,那麼答案爲ms(l2,r1)ms(l2,r1)

​ 2)如果一個在相交的區域,一個不在,那麼答案是 rmax+lmaxrmax+lmax,注意還要減去多算的元素,這個情況有兩個小情況,在左在右的問題。

​ 3)如果兩個端點都不在相交的區域,那麼答案類似於第一種情況,rmax(l1,l2)+Sum(l2+1,r11)+lmax(r1,r2)rmax(l_1,l_2)+Sum(l_2+1,r_1-1)+lmax(r_1,r_2)

最後,取個maxmax 就是答案了。

struct Segment{
    int lmax,rmax,sum,ms;
    Segment(){
        lmax = rmax = sum = ms = 0;
    }
}tree[N<<2];int q[N];
void built(int rt,int l,int r){
    if(l == r){
        q[l] = tree[rt].lmax = tree[rt].rmax = tree[rt].sum = tree[rt].ms = read();
        return ;
    }
    int mid = l + r >> 1;
    built(rt<<1,l,mid);
    built(rt<<1|1,mid +1,r);
    tree[rt].sum = tree[rt<<1].sum + tree[rt<<1|1].sum;
    tree[rt].lmax = max(tree[rt<<1].lmax,tree[rt<<1].sum + tree[rt<<1|1].lmax);
    tree[rt].rmax = max(tree[rt<<1|1].rmax,tree[rt<<1|1].sum + tree[rt<<1].rmax);
    tree[rt].ms = max(tree[rt<<1].ms,tree[rt<<1|1].ms);
    tree[rt].ms = max(tree[rt].ms,tree[rt<<1].rmax+tree[rt<<1|1].lmax);
}
Segment query(int L,int R,int rt,int l,int r){
    if(L > R) return Segment();//區間不合理情況,直接返回
    if(L<=l&&r<=R){
        return tree[rt];
    }
    int mid = l +r >>1;
    Segment a,b,ans;
    if(R<=mid) return query(L,R,rt<<1,l,mid);
    else if(L>mid) return query(L,R,rt<<1|1,mid+1,r);
    else {
        a = query(L,R,rt<<1,l,mid);
        b = query(L,R,rt<<1|1,mid+1,r);
        ans.sum = a.sum + b.sum;
        ans.lmax = max(a.lmax,a.sum + b.lmax);
        ans.rmax = max(b.rmax,b.sum + a.rmax);
        ans.ms = max(a.ms ,b.ms);
        ans.ms = max(ans.ms,a.rmax + b.lmax);
        return ans;
    }
}
int main(){
    int t = read();
    while(t--){
        int n = read();
        built(1,1,n);
        int m = read();
        while(m--){
            int l1 = read(),r1 = read(),l2 = read(),r2 = read();
            if(r1 < l2){
                cout<<query(r1+1,l2-1,1,1,n).sum + query(l1,r1,1,1,n).rmax + query(l2,r2,1,1,n).lmax<<endl;
            }
            else if(r1 == l2){
                if(l1 == r2) cout<<query(l1,r1,1,1,n).ms<<endl;
                else {
                    cout<<query(l1,r1,1,1,n).rmax + query(l2,r2,1,1,n).lmax - q[r1]<<endl;
                }
            }
            else {
                int a = query(l2,r1,1,1,n).ms;
                int b = query(l1,l2,1,1,n).rmax+query(l2,r1,1,1,n).lmax - q[l2];
                int c = query(l2,r1,1,1,n).rmax + query(r1,r2,1,1,n).lmax - q[r1];
                int d = query(l2+1,r1-1,1,1,n).sum + query(l1,l2,1,1,n).rmax + query(r1,r2,1,1,n).lmax;
                cout << max(max(a,d),max(b,c))<<endl;
            }
        }
    }
}

懶標記的運用
hihoCoder#1078 : 線段樹的區間修改
題意:
給你一個序列,有nn個數字,然後,你要進行區間修改,區間和查詢,區間修改是將[l,r][l,r]的數全變爲kk
思路:
一開始想的是隻需要將包含的區間的區加和修改一下就行,寫出來發現不對,如果這個區間在查詢時被破壞,下面的數字沒有發生改變,所以引進懶標記,用來告訴它的兒子們,他們是誰(心裏沒點阿拉伯數字

ll tree[N<<2],lzy[N<<2];
inline void push_up(int rt){
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
inline void push_down(int rt,int len){
    tree[rt<<1] =  lzy[rt]*(len-(len>>1));
    tree[rt<<1|1] = lzy[rt] * (len>>1);
    lzy[rt<<1] = lzy[rt];lzy[rt<<1|1] = lzy[rt];
    lzy[rt] = 0;
}
void built(int rt,int l,int r){
    if(l == r) {
        tree[rt] = read();
        return ;
    }
    int mid = l + r >> 1;
    built(rt<<1,l,mid);
    built(rt<<1|1,mid+1,r);
    push_up(rt);
}
void update(int L,int R,int dat,int rt,int l,int r){
    if(L<=l&&r <= R){
        tree[rt] = (r-l+1)*dat;
        lzy[rt] = dat;
        return ;
    }
    if(lzy[rt]) push_down(rt,r - l + 1);
    int mid = l + r >> 1;
    if(L<=mid) update(L,R,dat,rt<<1,l,mid);
    if(R>mid) update(L,R,dat,rt<<1|1,mid+1,r);
    push_up(rt);
}
int query(int L,int R,int rt,int l,int r){
    if(L<=l&&r<=R){
        return tree[rt];
    }
    if(lzy[rt]) push_down(rt,r-l+1);
    int mid = l + r >> 1;
    int ans=0;
    if(L<=mid) ans += query(L,R,rt<<1,l,mid);
    if(R>mid) ans += query(L,R,rt<<1|1,mid+1,r);
    return ans;
}
int main(){
    int n = read();
    built(1,1,n);
    int m = read();
    while(m--){
        int op = read(),l = read(),r = read();
        if(op == 1){
            int k = read();
            update(l,r,k,1,1,n);
        }
        else cout<<query(l,r,1,1,n)<<endl;
    }
}

權值線段樹

權值線段樹和普通線段樹的區別是,普通線段樹是維護給定的區間,而權值線段樹是維護值域中數的出現次數。
借用網上的圖片(侵刪
在這裏插入圖片描述
那麼我們可以用它來做什麼呢,可以求這個序列的第kk大/小問題。因爲至於的範圍可能很大,所以往往我們還伴隨着離散化。
我們找找這個序列的第9小(共有12個數,也可以說第4大)
我們從根節點往下找,左子樹值爲6 <9,所以我們往右子樹去,同時,減去左子樹的權值,變爲了3,然後在看其左子樹權值,爲4>3,所以往左子樹去,然後再比較權值,直到 到葉子節點,就是答案了,這裏答案就是6。
實現的話還是比較好寫的,舉個栗子。

P1138 第k小整數
在這裏插入圖片描述
思路:
用權值線段樹可以在loglog的時間內就可以找到了。

int a[N],b[N];
int tree[N<<2];
void update(int pos,int rt,int l,int r){
    if(l == r){
        //tree[rt] ++;
        tree[rt] = 1;
        return ;
    }
    int mid = l + r >> 1;
    if(pos <=mid) update(pos,rt<<1,l,mid);
    else update(pos,rt<<1|1,mid+1,r);
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
int kth(int k,int rt,int l,int r){
    if(l == r){
        return b[l];
    }
    int mid = l + r >> 1;
    if(k<=tree[rt<<1]) return kth(k,rt<<1,l,mid);
    else return kth(k-tree[rt<<1],rt<<1|1,mid+1,r);
}
int main(){
    int n = read(),k = read();
    for(int i = 1;i <= n;++i) a[i] = b[i] = read();
    sort(b+1,b+n+1);
    int m = unique(b+1,b+n+1) - b - 1;
    rep(i,1,n) a[i] = lower_bound(b+1,b+m+1,a[i]) - b;
    for(int i = 1;i <= n;++i) update(a[i],1,1,n);
    if(tree[1] < k) puts("NO RESULT");
    else cout << kth(k,1,1,n);
}

可持久化線段樹(主席樹)

持久化數據結構思想,就是保留整個操作的歷史,即,對一個線段樹進行操作之後,保留訪問操作前的線段樹的能力。
主席樹有一個很簡單的應用,查詢指定區間第kk大/小。
我們知道權值線段樹可以很簡單的求出整個序列的第kk大/小。我們原來的建立權值線段樹的過程,是不斷插入值,那麼,我們在插入的時候保留原來的權值線段樹,在這一個基礎上再插入新建一棵權值線段樹,這樣就建立了nn棵權值線段樹。
接下來有個性質,就是權值線段樹具有區間可減性,第rr棵權值線段樹上的權值減去第l1l-1棵權值線段樹上的權值,就是[l,r][l,r]這個區間所對應的權值線段樹。
但是建立nn棵權值線段樹,時空肯定會爆炸。那麼,我們能不能在上一棵權值線段樹的基礎上建立呢,看下圖(來自網絡,侵刪

在這裏插入圖片描述

(橙色爲原來的樹的路徑,藍色爲現在的)

比如上述圖片插入4,我們發現,只會影響一條路徑,而這個路徑的長度是log(n)log(n)的,那麼我們就可以整體以nlog(n)nlog(n)的空間複雜度建樹了。

關於可持久化線段樹的理解,感覺知乎上一位大佬說的很好,分享一下
在這裏插入圖片描述

一道模板題

在這裏插入圖片描述
直接上模板吧,模板來自oi-wiki。另外,建立主席樹時,不能用一般的堆式建樹,而需要動態開點,具體說就是需要保存左右兒子的下標,用結構體或者數組,應該是要不停建樹吧,而不單單是一棵線段樹了。還有關於數組的大小,oi-wike上說開n<<5n<<5,也就是32倍,也有的人喜歡開40倍,感覺32倍就足夠了吧。
Code:Code:

int a[N],b[N];
int rt[N<<5],ls[N<<5],rs[N<<5],tree[N<<5];
int tot;
int built(int l,int r){
    int root = ++ tot;
    if(l == r) return root;
    int mid = l + r >>1;
    ls[root] = built(l,mid);
    rs[root] = built(mid+1,r);
    return root ;
}
int update(int dat,int root,int l,int r){
    int dir = ++ tot;
    ls[dir] = ls[root],rs[dir] = rs[root],tree[dir] = tree[root] + 1;
    if(l == r) return dir;
    int mid = l + r >> 1;
    if(dat <= mid) ls[dir] = update(dat,ls[dir],l,mid);
    else rs[dir] = update(dat,rs[dir],mid+1,r);
    return dir;
}
int query(int root,int rot,int k,int l,int r){
    int x = tree[ls[root]] - tree[ls[rot]];
    if(l == r) return l;
    int mid = l + r >>1;
    if(k<=x) return query(ls[root],ls[rot],k,l,mid);
    else return query(rs[root],rs[rot],k-x,mid+1,r);
}
int  main(){
    int n = read(),m = read();
    rep(i,1,n) a[i] = b[i] = read();
    sort(b+1,b+n+1);
    int q = unique(b+1,b+n+1) - b - 1;
    rep(i,1,n) a[i] = lower_bound(b+1,b+q+1,a[i]) - b;
    rt[0] = built(1,n);
    rep(i,1,n) rt[i] = update(a[i],rt[i-1],1,n);
    rep(i,1,m){
        int l = read(),r = read(),k = read();
        printf("%d\n",b[query(rt[r],rt[l-1],k,1,n)]);
    }

}

bzoj 2588: Spoj 10628. Count on a tree
在這裏插入圖片描述
思路:
算是主席樹模板題吧,只不過是在樹上建立,還需要lcalca
這個題目強制在線處理。詢問你兩個節點之間路徑上的第kk小權值。這裏不妨假設根爲11,然後從根dfsdfs過程中建立權值線段樹。這樣就處理沒個節點到根路徑上的權值線段樹了,也是前綴和,然後若詢問u,vu,v兩個節點的第kk小,和序列上的第kk小類似,我們也要做差,來求出u,vu,v路徑上的權值線段樹。
(u,v)=rt[u]+rt[v]rt[lca(u,v)]rt[fa(lca(u,v))](u,v)=rt[u]+rt[v]-rt[lca(u,v)]-rt[fa(lca(u,v))]
這樣就求出u,vu,v路徑上的點出現的次數的權值線段樹了。
吐槽一下:woc,* * *,f * *k,因爲dfsdfsDFSDFS函數搞混,tm debug了很久,然後到處看別人的博客,感覺沒大問題啊,結果一直RE ,RE,RE…我裂開了。
不過倒也發現了一個問題,網上的博客中,雖然在bzojbzojAC了,但是在spoj上依舊RE,甚至連spoj的樣例都過不了,當然,也包括我的,在這個題目上花費了太多時間了,先跳過,以後再說。最後貼上能在spojspoj能過的神仙代碼。

int a[N],b[N];
int rt[N*40],tree[N*40],ls[N*40],rs[N*40];
int tot,n,m,q;
struct Edge
{
    int next;
    int to;
}edge[N<<1];
int head[N],cnt;
inline void add(int from,int to){
    edge[++cnt].next = head[from];
    edge[cnt].to = to;
    head[from] = cnt;
}
int depth[N+5],fa[N+5][30],lg[N+5];
int built(int l,int r){
    int root = ++ tot;
    if(l == r){
        return root;
    }
    int mid = l + r >> 1;
    ls[root] = built(l,mid);
    rs[root] = built(mid+1,r);
    return root;
}
int update(int dat,int root,int l,int r){
    int dir = ++ tot;
    ls[dir] = ls[root],rs[dir] = rs[root];tree[dir] = tree[root] + 1;
    if(l == r) return dir;
    int mid = l + r >> 1;
    if(dat <= mid) ls[dir] = update(dat,ls[dir],l,mid);
    else rs[dir] = update(dat,rs[dir],mid+1,r);
    return dir;
}
int FA[N];
void dfs(int x,int Fa){
    for(int i = head[x];i;i=  edge[i].next){
        int y = edge[i].to;
        if(y == Fa) continue;
        FA[y] = x;
        rt[y] = update(a[y],rt[x],1,q);
        dfs(y,x);
    }
}
void DFS(int f,int fath)
{
    depth[f]=depth[fath]+1;
    fa[f][0]=fath;
    for(int i=1;(1<<i)<=depth[f];i++)
      fa[f][i]=fa[fa[f][i-1]][i-1];
    for(int i=head[f];i;i=edge[i].next)
      if(edge[i].to!=fath)
        DFS(edge[i].to,f);
}
int lca(int x,int y)
{
    if(depth[x]<depth[y])
      swap(x,y);
    while(depth[x]>depth[y])
      x=fa[x][lg[depth[x]-depth[y]]-1];
    if(x==y)
      return x;
    for(int k=lg[depth[x]]-1;k>=0;k--)
      if(fa[x][k]!=fa[y][k])
        x=fa[x][k], y=fa[y][k];
    return fa[x][0];
}
int query(int u,int v,int la,int fath,int k,int l,int r){
    int x = tree[ls[u]] + tree[ls[v]] - tree[ls[la]] - tree[ls[fath]];
    if(l == r) return l;
    int mid = l + r >> 1;
    if(k <= x) return query(ls[u],ls[v],ls[la],ls[fath],k,l,mid);
    else return query(rs[u],rs[v],rs[la],rs[fath],k-x,mid+1,r);
}
int main(){
    n = read(),m = read();
    rep(i,1,n) a[i] = b[i] = read();
    //離散化
    sort(b+1,b+n+1);
    q = unique(b+1,b+n+1) - b - 1;
    rep(i,1,n) a[i] = lower_bound(b+1,b+q+1,a[i]) - b;
    rep(i,1,n-1){
        int u = read(),v = read();
        add(u,v);
        add(v,u);
    }
    rt[0] = built(1,q);
    rt[1] = update(a[1],rt[0],1,q);
    dfs(1,0);//建立主席樹
    //lca
    DFS(1,0);//1
    for(int i = 1;i<=n;++i){//2
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    }

    ll las = 0;
    rep(i,1,m){
        int u = read(),v = read(),k = read();
        u ^= las;
        int la = lca(u,v);
        las = b[query(rt[u],rt[v],rt[la],rt[FA[la]],k,1,q)];
        printf("%lld",las);
        if(i!=m) puts("");
    }
    return 0;

}

神仙代碼在此

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define fd(i,a,b) for (int (i)=(a);(i)>=(b);(i)--)
using namespace std;
const int M=100000+5;
int g[M][21],Next[M*2],head[M],to[M*2],rt[4001000],s[4001000],ls[4001000],rs[ 4001000];
int h[M],val[M],v,d[M],cd,tot,cnt;
void R(int &n)
{
    int t=0,p=1;char ch;
    for(ch=getchar ();!('0'<=ch && ch<='9');ch=getchar())
        if(ch=='-') p=-1;
    for(;'0'<=ch && ch<='9';ch=getchar()) t=t*10+ch-'0';
    n=t*p;
}
void add(int x,int y)
{
    to[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void ch(int x,int &y,int l,int r,int v)
{
    s[y=++cnt]=s[x]+1;
    if (l==r) return ;
    int m=l+r>>1;
    if (v<=m)
    {
        rs[y]=rs[x];
        ch(ls[x],ls[y],l,m,v);
    }
    else
    {
        ls[y]=ls[x];
        ch(rs[x],rs[y],m+1,r,v);
    }
}
int query(int x,int y,int z,int w,int l,int r,int k)
{
    if (l==r) return l;
    int tmp=s[ls[y]]+s[ls[z]]-s[ls[x]]-s[ls[w]];
    int m=l+r>>1;
    if (tmp>=k) return query(ls[x],ls[y],ls[z],ls[w],l,m,k);
    else return query(rs[x],rs[y],rs[z],rs[w],m+1,r,k-tmp);
}
void dfs(int x,int y)
{
    for (int i=head[x];i;i=Next[i])
    {
        v=to[i];
        if (y!=v)
        {
            d[v]=d[x]+1;
            g[v][0]=x;
            ch(rt[x],rt[v],1,cd,val[v]);
            dfs(v,x);
        }
    }
}
int  lca(int x,int y)
{
    if (d[x]<d[y]) swap(x,y);
    fd(k,20,0)
        if (d[g[x][k]]>d[y]) x=g[x][k];
    if (d[x]!=d[y]) x=g[x][0];
    fd(k,20,0)
        if (g[x][k]!=g[y][k])
            x=g[x][k],y=g[y][k];
    if (x!=y) return g[x][0];
    else return x;
}
int main()
{
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int n,m,x,y,z,k;
    R(n);R(m);
    fo(i,1,n)R(val[i]),h[i]=val[i];
    sort(h+1,h+1+n);
    cd=unique(h+1,h+1+n)-(h+1);
    fo(i,1,n)val[i]=lower_bound(h+1,h+1+cd,val[i])-h;
    fo(i,1,n-1)
    {
        R(x);R(y);
        add(x,y);add(y,x);
    }
    ch(rt[0],rt[1],1,cd,val[1]);
    d[1]=1;
    dfs(1,0);
    fo(j,1,20)
        fo(i,1,n)
            g[i][j]=g[g[i][j-1]][j-1];
    g[1][0]=0;
    int ans=0;
    while (m--)
    {
        R(x);R(y);R(k);
        z=lca(x,y);
        ans=h[query(rt[z],rt[x],rt[y],rt[g[z][0]],1,cd,k)];
        printf("%d\n",ans);
    }
    return 0;
}


待補:zkw線段樹,李超線段樹,掃描線

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章