【模板】线段树 主席树

关于线段树的一些算法的模板的汇总。

Luogu P3372 【模板】线段树 1

普通的线段树,区间加法,区间查询。

#include<cstdio>
using namespace std;
const int maxn = 100005;
int n,m,x,y,flag;
int l[4*maxn],r[4*maxn];
long long lazy[8*maxn],sum[4*maxn];

void build(int L,int R,int now) {
    lazy[now] = 0;
    l[now] = L;
    r[now] = R;
    if(L == R) {
        scanf("%lld",&sum[now]);
        return;
    }
    int mid = (L+R)/2;
    build(L,mid,now*2);
    build(mid+1,R,now*2+1);
    sum[now] = sum[now*2] + sum[now*2+1];
}

long long query(int L,int R,int now) {
    sum[now] += lazy[now] * (r[now] - l[now] + 1);
    lazy[now*2] += lazy[now];
    lazy[now*2+1] += lazy[now];
    lazy[now] = 0;
    if(l[now] == L && r[now] == R)return sum[now];
    int mid = (l[now]+r[now])/2;
    if(R <= mid) return query(L,R,now*2);
    else if(L >= mid+1) return query(L,R,now*2+1);
    else return query(L,mid,now*2)+query(mid+1,R,now*2+1);
}

void modify(int L,int R,long long d,int now) {
    if(l[now] == L && r[now] == R) {
        lazy[now] += d;
        return;
    }
    sum[now] += (long long)d*(R-L+1);
    int mid = (l[now]+r[now])/2;
    if(R <= mid) modify(L,R,d,now*2);
    else if(L >= mid+1) modify(L,R,d,now*2+1);
    else {
        modify(L,mid,d,now*2);
        modify(mid+1,R,d,now*2+1);
    }
}

int main() {
    scanf("%d%d",&n,&m);
    build(1,n,1);
    while(m) {
        m--;
        scanf("%d",&flag);
        if(flag == 1) {
            long long k;
            scanf("%d%d%lld",&x,&y,&k);
            modify(x,y,k,1);
        }
        if(flag == 2) {
            scanf("%d%d",&x,&y);
            printf("%lld
",query(x,y,1));
        }
    }
    return 0;
}
View Code

Luogu P3373 【模板】线段树 2

区间加法,区间乘法,区间查询。

注意:乘法标记优先于加法标记,打标记后直接(在原数的基础上)修改。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define MogeKo qwq
using namespace std;
#define ls (now<<1)
#define rs (now<<1|1)
const int maxn = 1e6+10;
int n,q,p,op,x,y;
long long k;
int l[maxn<<2],r[maxn<<2];
long long sum[maxn<<2],a[maxn<<3],m[maxn<<3];

void build(int L,int R,int now) {
    l[now] = L;
    r[now] = R;
    m[now] = 1;
    a[now] = 0;
    if(L == R) {
        scanf("%lld",&k);
        sum[now] = k%p;
        return;
    }
    int mid = (l[now] + r[now])>>1;
    build(L,mid,ls);
    build(mid+1,R,rs);
    sum[now] = (sum[ls] + sum[rs])%p;
}

void pushdown(int now) {
    if(m[now]==1 && a[now]==0) return;
    m[ls] = (m[ls]*m[now])%p;
    m[rs] = (m[rs]*m[now])%p;
    a[ls] = (a[ls]*m[now] + a[now])%p;
    a[rs] = (a[rs]*m[now] + a[now])%p;
    sum[ls] = (sum[ls]*m[now] + a[now]*(long long)(r[ls]-l[ls]+1))%p;
    sum[rs] = (sum[rs]*m[now] + a[now]*(long long)(r[rs]-l[rs]+1))%p;
    m[now] = 1;
    a[now] = 0;
}

void modify(int L,int R,int now,long long A,long long M) {
    pushdown(now);
    if(l[now] == L && r[now] == R) {
        m[now] = (m[now]*M)%p;
        a[now] = (a[now]*M + A)%p;
        sum[now] = (sum[now]*M + A*(long long)(r[now]-l[now]+1))%p;
        return;
    }
    int mid = (l[now] + r[now])>>1;
    if(R <= mid) modify(L,R,ls,A,M);
    else if(L >= mid+1) modify(L,R,rs,A,M);
    else {
        modify(L,mid,ls,A,M);
        modify(mid+1,R,rs,A,M);
    }
    sum[now] = (sum[ls] + sum[rs])%p;
}

long long query(int L,int R,int now) {
    if(l[now] == L && r[now] == R) {
        return sum[now]%p;
    }
    pushdown(now);
    int mid = (l[now] + r[now])>>1;
    if(R <= mid) return query(L,R,ls)%p;
    else if(L >= mid+1) return query(L,R,rs)%p;
    else return (query(L,mid,ls) + query(mid+1,R,rs))%p;
}

int main() {
    scanf("%d%d%d",&n,&q,&p);
    build(1,n,1);
    while(q--) {
        scanf("%d%d%d",&op,&x,&y);
        if(op == 3)
            printf("%lld
",query(x,y,1));
        else {
            scanf("%lld",&k);
            if(op == 1) modify(x,y,1,0,k);
            if(op == 2) modify(x,y,1,k,1);
        }
    }
    return 0;
}
View Code

Luogu P3834 【模板】可持久化线段树 1(主席树)

这道模板题是求静态区间第$k$大的值。

对于每个前缀,建立一棵权值线段树,记录每个数字在当前的区间中出现了多少次,以及当前的区间中共有几个数字。

建树时,判断新加的数在左区间还是右区间,把当前的树不需要修改的儿子连向前一个状态,给另一个需要修改的儿子建立一个新节点,重复上一步。

查询时,用前缀和的方式求解。求出左区间共有多少个数($sum[ls(v)]-sum[ls(u-1)]$),设为$lengthL$,然后与$k$的大小比较,

若$lengthL≥k$则在左儿子,否则在右儿子。注意,找右儿子时要把$k$减去左儿子已经找过的,即改为$k-lengthL$。

优化:给出的序列中可能不是每个数都出现的,一一对应效率较低,可以排序并去重。

(例如:给出1 2 5 5 8,可以直接建成(1)1,(2)2,(3)5,(4)8,而不是(1)1,(2)2,(3)3,(4)4,(5)5,(6)6,(7)7,(8)8 )

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MogeKo qwq
using namespace std;
const int maxn = 200005;
int a[maxn],b[maxn],T[maxn];
int L[maxn<<5],R[maxn<<5],sum[maxn<<5];
int cnt,n,q,m,t,u,v,k;

int build(int l,int r){
    int rt = ++cnt;
    int mid = (l+r)>>1;
    if(l < r){
        L[rt] = build(l,mid);
        R[rt] = build(mid+1,r);
    }
    return rt;
}
    
int update(int pre,int l,int r,int x){
    int rt = ++cnt;
    L[rt] = L[pre];
    R[rt] = R[pre];
    sum[rt] = sum[pre]+1;
    if(l < r){
        int mid = (l+r)>>1;
        if(x <= mid) L[rt] = update(L[pre],l,mid,x);
        else R[rt] = update(R[pre],mid+1,r,x);
    }
    return rt;
}

int query(int u,int v,int l,int r,int k){
    if(l == r)return l;
    int lenL = sum[L[v]] - sum[L[u]];
    int mid = (l+r)>>1;
    if(k <= lenL) return query(L[u],L[v],l,mid,k);
    else return query(R[u],R[v],mid+1,r,k-lenL);
}
    

int main(){
    scanf("%d%d",&n,&q);
    for(int i = 1;i <= n;i++){
        scanf("%d",&a[i]);
        b[i] = a[i];
    }
    sort(b+1,b+n+1);
    m = unique(b+1,b+n+1)-(b+1);
    T[0] = build(1,m);
    for(int i = 1;i <= n;i++){
        t = lower_bound(b+1,b+m+1,a[i])-b;
        T[i] = update(T[i-1],1,m,t);
    }
    for(int i = 1;i <= q;i++){
        scanf("%d%d%d",&u,&v,&k);
        printf("%d
",b[query(T[u-1],T[v],1,m,k)]);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/mogeko/p/11030929.html