线段树合并

权值线段树

一般的线段树维护的是区间上的信息,比如区间最大值,最小值,区间和等,维护的区间都对应数组的一段连续的下标。

而权值线段树于此不同。权值线段树是指以值域为区间的线段树,它维护的区间是一段值域,所以称之为权值线段树。

动态开点

因为权值线段树维护的是值域,而值域的范围可能很大,且值域中的许多位置是空的。我们可以省去这些空间,只将需要用的点表示出来即可。此时,叶节点的数量就是元素的数量。若元素值个数为 n n n,值域为 m m m,则每个叶节点的祖先最多 l o g m logm logm个,所以这棵树的节点数量也不会超过 n l o g m nlogm nlogm个。

线段树合并

线段树合并指将多棵线段树上的信息合并到一棵线段树上。每次合并两棵,合并后的信息就在其中一棵线段树上。

对于每次合并,我们从根节点往下依次遍历每个位置,如果某个位置两棵树都存在节点,则将一棵树上的信息加到另一棵树上,然后继续处理它们的左儿子和右儿子。如果某个位置只有一棵树有节点或者两棵树都没有节点,则只需处理当前位置,不需要再往下合并了。

code

void merge(int &r1,int r2,int l,int r){
    if(!r1||!r2){
        r1=r1+r2;return;
    }
    if(l==r){
        s[r1]+=s[r2];return;
    }
    int mid=(l+r)/2;
    merge(tr[r1].lc,tr[r2].lc,l,mid);
    merge(tr[r1].rc,tr[r2].rc,mid+1,r);
    s[r1]=s[tr[r1].lc]+s[tr[r1].rc];
}

例题

【HNOI2012】永无乡

这道题需要用到并查集和线段树合并。

开始时对每一座岛建立一棵线段树,第 i i i个叶节点为一表示重要程度为 i i i的岛与这个岛连通。维护区间叶节点数量 s s s r t [ i ] rt[i] rt[i]表示节点 i i i的权值线段树的根,然后对 M M M条边进行合并。比如对于边 ( a , b ) (a,b) (a,b),如果 a , b a,b a,b不在一个连通块,则设 x = f i n d ( a ) , y = f i n d ( b ) x=find(a),y=find(b) x=find(a),y=find(b),将 x x x的线段树和 y y y的线段树进行合并,然后 f a [ y ] = x fa[y]=x fa[y]=x,表示将 y y y的线段树合并到 x x x的线段树中。

对于 B B B操作,同上,并查集处理即可。对于 Q Q Q操作,查找 x x x的线段树,找到第 k k k小的叶节点即可。

code

#include<bits/stdc++.h>
using namespace std;
int n,m,q,tot=0,x,y,c[100005],re[100005],rt[100005],fa[100005],s[5000005];
char ch;
struct node{
    int lc,rc;
}tr[5000005];
int find(int ff){
    if(fa[ff]!=ff) fa[ff]=find(fa[ff]);
    return fa[ff];
}
void pt(int &k,int l,int r,int v){
    if(!k) k=++tot;
    if(l==r&&l==v){
        ++s[k];return;
    }
    if(l>v||r<v) return;
    if(l==r) return;
    int mid=(l+r)/2;
    if(v<=mid) pt(tr[k].lc,l,mid,v);
    else pt(tr[k].rc,mid+1,r,v);
    s[k]=s[tr[k].lc]+s[tr[k].rc];
}
void merge(int &r1,int r2,int l,int r){
    if(!r1||!r2){
        r1=r1+r2;return;
    }
    if(l==r){
        s[r1]+=s[r2];return;
    }
    int mid=(l+r)/2;
    merge(tr[r1].lc,tr[r2].lc,l,mid);
    merge(tr[r1].rc,tr[r2].rc,mid+1,r);
    s[r1]=s[tr[r1].lc]+s[tr[r1].rc];
}
void gt(int x,int y){
    int x1=find(x),x2=find(y);
    if(x1==x2) return;
    merge(rt[x1],rt[x2],1,n);
    fa[x2]=x1;
}
int find(int k,int l,int r,int v){
    if(!k) return -1;
    if(l==r){
        if(v==1) return re[l];
        return -1;
    }
    int mid=(l+r)/2;
    if(v<=s[tr[k].lc]&&tr[k].lc) return find(tr[k].lc,l,mid,v);
    return find(tr[k].rc,mid+1,r,v-s[tr[k].lc]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);
        re[c[i]]=i;fa[i]=i;
        pt(rt[i],1,n,c[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        gt(x,y);
    }
    scanf("%d",&q);
    while(q--){
        ch=getchar();
        while(ch!='B'&&ch!='Q') ch=getchar();
        scanf("%d%d",&x,&y);
        if(ch=='B'){
            gt(x,y);
        }
        else{
            x=find(x);
            printf("%d\n",find(rt[x],1,n,y));
        }
    }
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线段树(Segment Tree)是一种数据结构,用于高效地支持区间查询和更新操作。在C语言中,线段树通常用来处理数组或区间问题,它将原始的区间操作抽象为树状结构,使得查询和修改操作的时间复杂度可以降低到O(logn)。 合并线段树的操作一般涉及到两个子树的合并,比如在一个区间合并树中,可能需要合并左子树和右子树,然后更新根节点的值。这通常涉及到递归,首先处理子节点,然后将结果合并。 以下是一个简单的线段树合并操作的C语言代码示例,这里假设线段树是二维数组,每个元素代表一个区间的值: ```c #include <stdio.h> #define MAXN 100000 // 假设最大节点数 int tree[MAXN << 2]; // 用一个数组存储整个线段树 // 合并两个区间 [l, r] 和 [m, n] void merge(int node, int l, int r, int m, int n, int value) { if (l > n || r < m) return; // 如果区间不重叠,直接返回 if (l >= m && r <= n) { // 区间完全包含于[l, r] tree[node] = value; return; } int mid = (l + r) / 2; merge(node * 2, l, mid, m, min(n, mid), value); // 更新左子树 merge(node * 2 + 1, mid + 1, r, max(m, mid + 1), n, value); // 更新右子树 tree[node] = tree[node * 2] + tree[node * 2 + 1]; // 合并子树 } // 使用示例 void update(int node, int l, int r, int pos, int val) { merge(node, l, r, pos, pos, val); } // 查询区间[l, r]的和 int query(int node, int l, int r, int L, int R) { if (L <= l && r <= R) return tree[node]; int mid = (l + r) / 2, sum = 0; if (L <= mid) sum += query(node * 2, l, mid, L, R); if (R > mid) sum += query(node * 2 + 1, mid + 1, r, L, R); return sum; } int main() { // 初始化或填充线段树后,再使用update和query方法进行操作 // 例如: // update(1, 0, N - 1, 3, 5); // 将索引为3的元素值更新为5 // int sum = query(1, 0, N - 1, 1, 4); // 计算区间[1, 4]的和 return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值