线段树分裂与合并 学习笔记

线段树是很基本的一种数据结构,也是一种很强大的数据结构,它的衍生有权值线段树,可持久化线段树(主席树).还有现在要介绍的分裂合并版本的线段树.为了方便,下面的文章都用合并树来代替这一长串的称呼.
这种线段树的一般套路都是维护权值,而不是维护序列.一般来说,合并树的基本操作就以下两种:
add:

void add(int &now,int l,int r,int p,int v){
    
    
    if(!now) now = ++tot;
    tree[now].sum += v;
    if(l >= r) return;
    int mid = l + r >> 1;
    if(p <= mid) add(lson(now),l,mid,p,v);
    else add(rson(now),mid+1,r,p,v);
}

merge:

int merge(int x,int y){
    
    
    if(!x||!y) return x+y;
    tree[x].sum += tree[y].sum;
    lson(x) = merge(lson(x),lson(y));
    rson(x) = merge(rson(x),rson(y));
    return x;
}

要注意的是,为了节省空间等原因,我们不再用2×i,2×i+1的方式记录左右儿子,而是新开两个指针变量来记录左右儿子.而且也不在线段树中储存左右区间范围,而是在传递的时候增加两个变量记录区间范围.这里的add操作和主席树的add操作有相似之处,merge操作和fhq_treap有相似之处.如果学过这两种数据结构应该比较好理解这两个代码.没学过看一下估计也理解了.
那有了这两种操作之后怎么去写这类型的题目呢?下面给几道例题大伙感受一下.
Promotion Counting P
我们先把1看做根节点.如果只求ans[1]很好做,我们只要开一个线段树或者树状数组,储存所有的子树中的权值,然后求a[1]+1-maxvalue有多少个数就ok了.
那么要求全部的ans数组怎么操作呢?我们考虑一开始为每一个节点都开一颗合并树.因为是动态开点,所以一棵树的空间复杂度是O(logn)的.然后从叶子节点开始,一层一层往上合并,就能跟求ans[1]一样求出每一个节点的答案.
代码:

#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#define lson(i) tree[i].l
#define rson(i) tree[i].r
#include <bits/stdc++.h>

using namespace std;
const int N = 2e5+10;

inline void read(int &a){
    
    
    int x = 0,f=1;char ch = getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){
    
    x=x*10+ch-'0';ch=getchar();}
    a = x*f;
}
struct Tree{
    
    
    int l,r,sum;
}tree[N*20];
int tn,n,m,ct,nxt[N],head[N],to[N],w[N],ans[N];
vi all;
void addedge(int x,int y){
    
    
    nxt[++ct] = head[x];head[x] = ct;to[ct] = y;
}
int getpos(int x){
    
    
    return lower_bound(ALL(all),x) - all.begin();
}
int tot,root[N];

void add(int &now,int l,int r,int p,int v){
    
    
    if(!now) now = ++tot;
    tree[now].sum += v;
    if(l >= r) return;
    int mid = l + r >> 1;
    if(p <= mid) add(lson(now),l,mid,p,v);
    else add(rson(now),mid+1,r,p,v);
}

void pre(int u,int fa){
    
    
    root[u] = ++tot;
    add(root[u],1,tn,getpos(w[u]),1);
    for(int i = head[u];i;i = nxt[i]){
    
    
        int y = to[i];
        if(y == fa) continue;
        pre(y,u);
    }
}
int merge(int x,int y){
    
    
    if(!x||!y) return x+y;
    tree[x].sum += tree[y].sum;
    lson(x) = merge(lson(x),lson(y));
    rson(x) = merge(rson(x),rson(y));
    return x;
}
int query(int &now,int l,int r,int ll,int rr){
    
    
    if(ll > rr) return 0;
    if(!now) now = ++tot;
    if(l >= ll && r <= rr) return tree[now].sum;
    int mid = l + r >> 1;
    if(rr <= mid) return query(lson(now),l,mid,ll,rr);
    else if(ll > mid) return query(rson(now),mid+1,r,ll,rr);
    else return query(lson(now),l,mid,ll,mid) + query(rson(now),mid+1,r,mid+1,rr);
}
void solve(int u,int fa){
    
    
    int r = 0;
    for(int i = head[u];i;i = nxt[i]){
    
    
        int y = to[i];
        if(y == fa) continue;
        solve(y,u);
        r = merge(r,root[y]);
    }
    ans[u] = query(r,1,tn,getpos(w[u])+1,tn);
    root[u] = merge(root[u],r);
}

int main(){
    
    
    read(n);
    all.pb(INT_MIN);
    fir(i,1,n) read(w[i]),all.pb(w[i]);
    sort(ALL(all));
    all.erase(unique(ALL(all)),all.end());
    tn = all.size()-1;
    fir(i,1,n-1){
    
    
        int x;
        read(x);
        addedge(i+1,x);addedge(x,i+1);
    }
    pre(1,0);
    solve(1,0);    
    fir(i,1,n) printf("%d\n",ans[i]);
    
    return 0;
}    

ROT-Tree Rotations
先序遍历之后的序列有一个特点,子节点是不会影响父节点的逆序对的.所以我们可以每一层贪心的求子树的结构.和上一题一样,每一个节点开一颗合并树.
然后因为是一颗二叉树,子节点只有两种情况.那么为了快速统计这两种情况的逆序对,可以直接在合并的时候求出逆序对的数量.考虑x和y两颗子树合并之后要么x在左边要么y在左边,如果是x在左边,产生的逆序对就是tree[rson(x)].sum*tree[lson(y)].sum.y在左边同理.然后递归处理就ok了.
代码:

#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#define lson(i) tree[i].l
#define rson(i) tree[i].r
#include <bits/stdc++.h>

using namespace std;
const int N = 2e5+10;

inline void read(int &a){
    
    
    int x = 0,f=1;char ch = getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){
    
    x=x*10+ch-'0';ch=getchar();}
    a = x*f;
}
struct Tree{
    
    
    int l,r;
    LL sum;
}tree[N*22];

int ct,tot,root[N],n,ro;
void add(int &now,int l,int r,int p){
    
    
    if(!now) now = ++tot;
    tree[now].sum++;
    if(l >= r) return;
    int mid = l + r >> 1;
    if(p <= mid) add(lson(now),l,mid,p);
    else add(rson(now),mid+1,r,p);
}
LL sum1,sum2;
int merge(int x,int y){
    
    
    if(!x || !y) return x+y;
    tree[x].sum += tree[y].sum;
    sum1 += tree[rson(x)].sum*tree[lson(y)].sum;
    sum2 += tree[lson(x)].sum*tree[rson(y)].sum;
    lson(x) = merge(lson(x),lson(y));
    rson(x) = merge(rson(x),rson(y));
    return x;
}
LL ans;
void input(int &now){
    
    
    int ls,rs;
    int x;
    read(x);
    if(!x){
    
    
        input(ls);
        input(rs);
        sum1 = sum2 = 0;
        now = ls;
        now = merge(ls,rs);
        ans += min(sum1,sum2);
    }
    else add(root[x],1,n,x),now = root[x]; 
}

int main(){
    
    
    read(n);
    input(ro);
    cout << ans << endl;
    
    return 0;
}    

永无乡
这题用fhq_treap,splay也能做,但是用合并树应该是最简单的一种解法.而且这题可以引出一个思考,合并树到底什么时候能派上用场?
类比一般的题目,如果是这类K小值全局问题不涉及区间的时候,一般会用权值线段树直接维护,如果是区间问题,fhq_treap,splay等结构就可以派上用场了.而合并树是一个很神奇的数据结构,它可以快速的求解"区间全局"问题.像前两题,我们会发现都是用了类似DP的思想,先求解子问题,然后在一个一个子问题中,该问题被转化成为了全局问题.
说了那么多废话,说回这题.我们可以用一个并查集来维护每一个连通块.然后在每个并查集中维护一个合并树,在并查集的union操作的时候顺便把树也给合并了.然后用权值线段树求全局k小值的做法应该就不必多说了.


#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#define lson(i) tree[i].l
#define rson(i) tree[i].r
#include <bits/stdc++.h>

using namespace std;
const int N = 2e5+10;

inline void read(int &a){
    
    
    int x = 0,f=1;char ch = getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){
    
    x=x*10+ch-'0';ch=getchar();}
    a = x*f;
}

struct Tree{
    
    
    int l,r,sum;
}tree[N*15];

int tot,root[N],n,m,w[N],fa[N],pos[N];

int find(int x){
    
    
    int r = x;
    while(r != fa[r]) r = fa[r];
    int i = x,j = x;
    while(i != fa[i]){
    
    
        j = fa[i];
        fa[i] = r;
        i = j;
    }
    return r;
}

void add(int &now,int l,int r,int p){
    
    
    if(!now) now = ++tot;
    tree[now].sum++;
    if(l >= r) return;
    int mid = l+r>>1;
    if(p<=mid) add(lson(now),l,mid,p);
    else add(rson(now),mid+1,r,p);
}

int merge(int x,int y){
    
    
    if(!x || !y) return x+y;
    tree[x].sum += tree[y].sum;
    lson(x) = merge(lson(x),lson(y));
    rson(x) = merge(rson(x),rson(y));
    return x;
}

void unit(int x,int y){
    
    
    int fx = find(x),fy = find(y);
    if(fx == fy) return;
    fa[fy] = fx;
    root[fx] = merge(root[fx],root[fy]);
    return;
}

int query(int &now,int l,int r,int k){
    
    
    if(!now) now = ++tot;
    if(tree[now].sum < k) return 0;
    if(l >= r) return l;
    int mid = l + r >> 1;
    int sum = tree[lson(now)].sum;
    if(sum >= k) return query(lson(now),l,mid,k);
    else return query(rson(now),mid+1,r,k-sum);
}

int main(){
    
    
    read(n);read(m);
    fir(i,1,n) read(w[i]),fa[i] = i,pos[w[i]] = i,add(root[i],1,n,w[i]);
    fir(i,1,m){
    
    
        int x,y;
        read(x);read(y);
        unit(x,y);
    }
    pos[0] = -1;
    read(m);
    fir(i,1,m){
    
    
        char op;
        while(op = getchar()) if(isalpha(op)) break;
        int x,y;
        read(x);read(y);
        if(op == 'Q') printf("%d\n",pos[query(root[find(x)],1,n,y)]);
        else unit(x,y); 
    }
    
    return 0;
}    

雨天的尾巴
做了那么多题,这题想必也没啥难度了.不过需要点亮一个前置知识:树上差分.网上随便找个博客看看就好.
然后说回这题,老套路,给每个点开个合并树,然后用树上差分的做法.正确的修改对应的点.然后维护一下树就好了.

#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#define lson(i) tree[i].l
#define rson(i) tree[i].r
#include <bits/stdc++.h>

using namespace std;
const int N = 2e5+10;
const int tn = 1e5+2;
const int M = 30;
inline void read(int &a){
    
    
    int x = 0,f=1;char ch = getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){
    
    x=x*10+ch-'0';ch=getchar();}
    a = x*f;
}
struct Tree{
    
    
    int l,r,mx,id;
}tree[N*25];
int dp[M][N],d[N],n,m,ct,nxt[N],head[N],to[N],fa[N],root[N],maxlen = 29;
void addedge(int x,int y){
    
    
    nxt[++ct] = head[x];head[x] = ct;to[ct] = y;
}
void prework(){
    
    
    queue<int> q;
    int s = 1;
    q.push(s);
    d[s] = 1;
    while(q.size()){
    
    
        int u = q.front();
        q.pop();
        for(int i = head[u];i;i = nxt[i]){
    
    
            int y = to[i];
            if(d[y]) continue;
            dp[0][y] = u;
            d[y] = d[u] + 1;
            fir(k,1,maxlen)
                dp[k][y] = dp[k-1][dp[k-1][y]];
            q.push(y);
        }
    }
}

int lca(int x,int y){
    
    
    if(d[x] > d[y]) return lca(y,x);
    afir(i,maxlen,0)
        if(d[dp[i][y]] >= d[x]) y = dp[i][y];
    if(x == y) return x;
    afir(i,maxlen,0)
        if(dp[i][x] != dp[i][y]) x = dp[i][x],y = dp[i][y];
    return dp[0][x];
}

int tot;
void push_up(int i){
    
    
    if(tree[lson(i)].mx >= tree[rson(i)].mx){
    
    
        tree[i].mx = tree[lson(i)].mx;
        tree[i].id = tree[lson(i)].id;
    }
    else{
    
    
        tree[i].mx = tree[rson(i)].mx;
        tree[i].id = tree[rson(i)].id;
    }
}
void add(int &now,int l,int r,int p,int v){
    
    
    if(!now) now = ++tot;
    if(l >= r){
    
    
        tree[now].mx += v;
        if(!tree[now].mx) tree[now].id = 0;
        else tree[now].id = l;
        return;
    }
    int mid = l + r>>1;
    if(p <= mid) add(lson(now),l,mid,p,v);
    else add(rson(now),mid+1,r,p,v);
    push_up(now);
}
int merge(int l,int r,int x,int y){
    
    
    if(!x || !y) return x + y;
    if(l == r){
    
    
        tree[x].mx += tree[y].mx;
        if(!tree[x].mx) tree[x].id = 0;
        else tree[x].id = l;
        return x;
    }
    int mid = l + r >> 1;
    lson(x) = merge(l,mid,lson(x),lson(y));
    rson(x) = merge(mid+1,r,rson(x),rson(y));
    push_up(x);
    return x;
}
void pre(int u){
    
    
    for(int i=head[u];i;i=nxt[i]){
    
    
        int y = to[i];
        if(y == fa[u]) continue;
        fa[y] = u;
        pre(y);
    }
}
int ans[N];
void dfs(int u){
    
    
    for(int i=head[u];i;i=nxt[i]){
    
    
        int y = to[i];
        if(y == fa[u]) continue;
        dfs(y);
        root[u] = merge(1,tn,root[u],root[y]);
    }
    ans[u] = tree[root[u]].id;
}

int main(){
    
    
    read(n);read(m);
    fir(i,1,n-1){
    
    
        int x,y;
        read(x);read(y);
        addedge(x,y);addedge(y,x);
    }
    prework();
    pre(1);
    fir(i,1,m){
    
    
        int x,y,z;
        read(x);read(y);read(z);
        int Lca = lca(x,y);
        add(root[x],1,tn,z,1);add(root[y],1,tn,z,1);add(root[Lca],1,tn,z,-1);
        if(fa[Lca]) add(root[fa[Lca]],1,tn,z,-1);
    }
    dfs(1);
    fir(i,1,n) cout << ans[i] << endl;
    
    return 0;
}  

猜你喜欢

转载自blog.csdn.net/weixin_45590210/article/details/108819583