线段树总结(不看后悔系列)

普通线段树

线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。

线段树可以在 O ( l o g N ) O(log N) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。(注意:查询时一定要注意 查询的范围不能超过所建立线段树区间的范围)

线段树维护的信息,需要满足区间可加性,即能以可以接受的速度合并信息和修改信息,包括在使用懒惰标记时,标记也要满足可加性(例如取模就不满足可加性,对 4 4 取模然后对 3 3 取模,两个操作就不能合并在一起做)。看下图理解下(图片来自网络
图片来自网络

模板

节点 数据向上跟新
将子节点的值更新到父节点。

//对于区间求和
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取模卡我。。。。

这个题目维护两个 l a z y lazy 标记,加法标记和乘法标记。

然后区间更新时,记住乘法优先的原则。

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、这两个区间是相离的,那么这个最后查询的区间,肯定会包括 [ r 1 + 1 , l 2 1 ] [r_1+1,l_2-1] ,所以我们要求的答案为 r m a x ( l 1 , r 1 ) + S u m ( r 1 + 1 , l 2 1 ) + l m a x ( l 2 , r 2 ) rmax(l_1,r_1)+Sum(r_1+1,l_2-1)+lmax(l_2,r_2)

2、这两个区间是相交的( r 1 = l 2 r_1=l_2 ),

​ 1)如果这两个区间重合,那么答案是 m s ( l 1 , r 1 ) ms(l_1,r_1)

​ 2)不重合的情况,答案为 r m a x ( l 1 , r 1 ) + l m a x ( l 2 , r 2 ) a [ r 1 ] rmax(l_1,r_1)+lmax(l2,r2)-a[r_1] ,因为我们多算了一次 r 1 r_1 ,所以要减去

3、相交的情况,有点复杂

​ 1)如果两个端点在相交的区域,那么答案为 m s ( l 2 , r 1 ) ms(l2,r1)

​ 2)如果一个在相交的区域,一个不在,那么答案是 r m a x + l m a x rmax+lmax ,注意还要减去多算的元素,这个情况有两个小情况,在左在右的问题。

​ 3)如果两个端点都不在相交的区域,那么答案类似于第一种情况, r m a x ( l 1 , l 2 ) + S u m ( l 2 + 1 , r 1 1 ) + l m a x ( r 1 , r 2 ) rmax(l_1,l_2)+Sum(l_2+1,r_1-1)+lmax(r_1,r_2)

最后,取个 m a x max 就是答案了。

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 : 线段树的区间修改
题意:
给你一个序列,有 n n 个数字,然后,你要进行区间修改,区间和查询,区间修改是将 [ l , r ] [l,r] 的数全变为 k k
思路:
一开始想的是只需要将包含的区间的区加和修改一下就行,写出来发现不对,如果这个区间在查询时被破坏,下面的数字没有发生改变,所以引进懒标记,用来告诉它的儿子们,他们是谁(心里没点阿拉伯数字

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;
    }
}

权值线段树

权值线段树和普通线段树的区别是,普通线段树是维护给定的区间,而权值线段树是维护值域中数的出现次数。
借用网上的图片(侵删
在这里插入图片描述
那么我们可以用它来做什么呢,可以求这个序列的第 k k 大/小问题。因为至于的范围可能很大,所以往往我们还伴随着离散化。
我们找找这个序列的第9小(共有12个数,也可以说第4大)
我们从根节点往下找,左子树值为6 <9,所以我们往右子树去,同时,减去左子树的权值,变为了3,然后在看其左子树权值,为4>3,所以往左子树去,然后再比较权值,直到 到叶子节点,就是答案了,这里答案就是6。
实现的话还是比较好写的,举个栗子。

P1138 第k小整数
在这里插入图片描述
思路:
用权值线段树可以在 l o g log 的时间内就可以找到了。

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);
}

可持久化线段树(主席树)

持久化数据结构思想,就是保留整个操作的历史,即,对一个线段树进行操作之后,保留访问操作前的线段树的能力。
主席树有一个很简单的应用,查询指定区间第 k k 大/小。
我们知道权值线段树可以很简单的求出整个序列的第 k k 大/小。我们原来的建立权值线段树的过程,是不断插入值,那么,我们在插入的时候保留原来的权值线段树,在这一个基础上再插入新建一棵权值线段树,这样就建立了 n n 棵权值线段树。
接下来有个性质,就是权值线段树具有区间可减性,第 r r 棵权值线段树上的权值减去第 l 1 l-1 棵权值线段树上的权值,就是 [ l , r ] [l,r] 这个区间所对应的权值线段树。
但是建立 n n 棵权值线段树,时空肯定会爆炸。那么,我们能不能在上一棵权值线段树的基础上建立呢,看下图(来自网络,侵删

在这里插入图片描述

(橙色为原来的树的路径,蓝色为现在的)

比如上述图片插入4,我们发现,只会影响一条路径,而这个路径的长度是 l o g ( n ) log(n) 的,那么我们就可以整体以 n l o g ( n ) nlog(n) 的空间复杂度建树了。

关于可持久化线段树的理解,感觉知乎上一位大佬说的很好,分享一下
在这里插入图片描述

一道模板题

在这里插入图片描述
直接上模板吧,模板来自oi-wiki。另外,建立主席树时,不能用一般的堆式建树,而需要动态开点,具体说就是需要保存左右儿子的下标,用结构体或者数组,应该是要不停建树吧,而不单单是一棵线段树了。还有关于数组的大小,oi-wike上说开 n < < 5 n<<5 ,也就是32倍,也有的人喜欢开40倍,感觉32倍就足够了吧。
C o d e : 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
在这里插入图片描述
思路:
算是主席树模板题吧,只不过是在树上建立,还需要 l c a lca
这个题目强制在线处理。询问你两个节点之间路径上的第 k k 小权值。这里不妨假设根为 1 1 ,然后从根 d f s dfs 过程中建立权值线段树。这样就处理没个节点到根路径上的权值线段树了,也是前缀和,然后若询问 u , v u,v 两个节点的第 k k 小,和序列上的第 k k 小类似,我们也要做差,来求出 u , v u,v 路径上的权值线段树。
( u , v ) = r t [ u ] + r t [ v ] r t [ l c a ( u , v ) ] r t [ f a ( l c a ( u , v ) ) ] (u,v)=rt[u]+rt[v]-rt[lca(u,v)]-rt[fa(lca(u,v))]
这样就求出 u , v u,v 路径上的点出现的次数的权值线段树了。
吐槽一下:woc,* * *,f * *k,因为 d f s dfs D F S DFS 函数搞混,tm debug了很久,然后到处看别人的博客,感觉没大问题啊,结果一直RE ,RE,RE…我裂开了。
不过倒也发现了一个问题,网上的博客中,虽然在 b z o j bzoj AC了,但是在spoj上依旧RE,甚至连spoj的样例都过不了,当然,也包括我的,在这个题目上花费了太多时间了,先跳过,以后再说。最后贴上能在 s p o j spoj 能过的神仙代码。

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线段树,李超线段树,扫描线

发布了636 篇原创文章 · 获赞 38 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_43408238/article/details/104907680
今日推荐