线段树合并与平衡树合并算法思路

线段树合并

线段树合并,听起来很高端,其实就是把两棵线段树相加。

引用一下一位大佬的图:
线段树合并

具体地说,每次合并操作我们考虑将 \(o_2\) 这棵树的信息加到 \(o_1\) 上,那么我们就遍历二者区间。

  • 对于 \(o_1\) 没有但 \(o_2\) 有信息的区间,直接将 \(o_2\) 树上的节点接过来以节省时间。
  • 对于二者都有的区间,我们选择遍历到叶子节点,然后信息依次递推回去。
  • 对于 \(o_1\) 有但 \(o_2\) 没有信息的区间,不用动他。

可以发现,这个合并思路跟 \(FHQ-Treap\) 有异曲同工之妙,所以二者写法也有些相似之处。

例题1: 洛谷P3605

/*2022.11.27 洛谷AC
该代码目标:对于树上每个点,求其子树中有多少个权值比他大的点。
利用权值线段树,从下往上建树,将左右子树的线段树合并,然后直接查询即可。
*/

#include <bits/stdc++.h>
using namespace std;
#define N 1000010
#define ll long long

template <class T>
inline void read(T& a){
	T x = 0, s = 1;
	char c = getchar();
	while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); }
	a = x * s;
	return ;
}

int n;
struct Ability{
  int w; 
  int id; 

  bool cmp(Ability a, Ability b){
    return a.w < a.w; 
  }

} A[N]; 

bool cmp(Ability a, Ability b){
    return a.w < b.w; 
  }

int p[N]; 

map <int, int> g; 

struct node{
  int u, v, next;
} t[N << 1];
int head[N];

int bian = 0;
inline void addedge(int u, int v){
  t[++bian] = (node){u, v, head[u]}, head[u] = bian;
  return ; 
}

int root[N]; 
int tot = 0; 
int ans[N]; 

struct Segment_tree{
  struct node{
    int w; 
    int lson, rson; 
  } t[N << 2]; 

  inline void pushup(int o){
    t[o].w = t[t[o].lson].w + t[t[o].rson].w; 
    return ; 
  }

  void insert(int &o, int l, int r, int x, int k){
    if(!o) o = ++tot; 
    if(l == r && l == x){
      t[o].w += k;
      return ; 
    }
    int mid = l + r >> 1;
    if(mid >= x) insert(t[o].lson, l, mid, x, k);
    else insert(t[o].rson, mid + 1, r, x, k);
    pushup(o);
    return ; 
  }

  int query(int &o, int l, int r, int in, int end){
    if(!o) o = ++tot; 
    if(l >= in && r <= end) return t[o].w; 
    if(l > end || r < in) return 0; 
    int mid = l + r >> 1;
    int sum = 0;
    if(mid >= in) sum += query(t[o].lson, l, mid, in, end);
    if(mid < end) sum += query(t[o].rson, mid + 1, r, in, end);
    return sum; 
  }

  int merge(int o1, int o2, int l, int r){  // 跟 FHQ-Treap 一样的
    if(!o1 || !o2) return o1 + o2;  // 有一侧没有的话,直接把下面的接上去,省去了重复计算
    if(l == r){
      t[o1].w += t[o2].w;
      return o1; 
    }
    int mid = l + r >> 1; 
    t[o1].lson = merge(t[o1].lson, t[o2].lson, l, mid); 
    t[o1].rson = merge(t[o1].rson, t[o2].rson, mid + 1, r); 
    pushup(o1);
    return o1; 
  }

} tree; 

void dfs(int u){
  tree.insert(root[u], 1, n, p[u], 1);   // 这里写 n 是因为最多只有 n 种不同的权值,一般性地应该写权值中的最大值
  for(int i = head[u]; i; i = t[i].next){
    int v = t[i].v;
    dfs(v); 
    root[u] = tree.merge(root[u], root[v], 1, n); 
  }
  ans[u] = tree.query(root[u], 1, n, p[u] + 1, n);
  return ; 
}

int main(){
  // freopen("hh.txt", "r", stdin);
  read(n);
  for(int i = 1; i <= n; i++){
    read(A[i].w); 
    A[i].id = i; 
  }

  sort(A + 1, A + n + 1, cmp);
  int id = 0; 
  for(int i = 1; i <= n; i++){
    if(!g[A[i].w]) g[A[i].w] = ++id; 
  }
  for(int i = 1; i <= n; i++)
    p[A[i].id] = g[A[i].w];  // 离散化操作,可以忽略
  
  for(int i = 2; i <= n; i++){
    int x; read(x); 
    addedge(x, i); 
  }

  dfs(1); 
  for(int i = 1; i <= n; i++)
    printf("%d\n", ans[i]);
  return 0;
}

例题2 洛谷P3224 [HNOI2012]永无乡

先考虑用并查集维护每个点的连通情况,然后合并权值线段树。
权值线段树查第 \(k\) 大?线段树上二分即可。

/*线段树合并写法*/
#include <bits/stdc++.h>
using namespace std;
#define N 1000010
#define ll long long

template <class T>
inline void read(T& a){
	T x = 0, s = 1;
	char c = getchar();
	while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); }
	a = x * s;
	return ;
}

int n, m; 
int p[N]; 

int fa[N];
int find_fa(int x){
  return x == fa[x] ? x : fa[x] = find_fa(fa[x]); 
}

int root[N];

struct Segment_tree{
  struct node{
    int w; 
    int lson, rson; 
  } t[N << 2];

  int tot;
  Segment_tree(int tot = 0){
    this->tot = tot;
    return ; 
  }

  inline void pushup(int o){
    t[o].w = t[t[o].lson].w + t[t[o].rson].w;
    return ; 
  }

  void update(int &o, int l, int r, int x, int k){
    if(!o) o = ++tot;
    if(l == r && l == x){
      t[o].w += k;
      return ; 
    }
    int mid = l + r >> 1;
    if(mid >= x) update(t[o].lson, l, mid, x, k);
    else update(t[o].rson, mid + 1, r, x, k);
    pushup(o);
    return ; 
  }

  int query_kth(int o, int l, int r, int in, int end, int k){
    if(!o) return 0; 
    if(l == r) return l;
    int mid = l + r >> 1;
    if(t[t[o].lson].w >= k) return query_kth(t[o].lson, l, mid, in, end, k);
    else return query_kth(t[o].rson, mid + 1, r, in, end, k - t[t[o].lson].w); 
  }

  int merge(int &o1, int &o2, int l, int r){
    if(!o1 || !o2) return o1 + o2; 
    if(l == r){
      t[o1].w += t[o2].w;
      return o1; 
    }
    // printf("o1: %d  o2: %d  w1: %d  w2: %d\n", o1, o2, t[o1].w, t[o2].w); 
    int mid = l + r >> 1;
    t[o1].lson = merge(t[o1].lson, t[o2].lson, l, mid);
    t[o1].rson = merge(t[o1].rson, t[o2].rson, mid + 1 ,r);
    pushup(o1);
    return o1; 
  }

} tree; 

void comb(int u, int v){
  int fau = find_fa(fa[u]), fav = find_fa(fa[v]);
  if(fau == fav) return ; 
  if(tree.t[root[fau]].w < tree.t[root[fav]].w) swap(fau, fav);
  root[fau] = tree.merge(root[fau], root[fav], 1, n);
  fa[fav] = fau;
  return ; 
}

int rev[N]; 

int main(){
  // freopen("hh.txt", "r", stdin); 
  read(n), read(m);
  for(int i = 1; i <= n; i++){
    fa[i] = i; 
    read(p[i]); 
    rev[p[i]] = i; 
    tree.update(root[i], 1, n, p[i], 1); 
  }
  rev[0] = -1; 
  for(int i = 1; i <= m; i++){
    int u, v;
    read(u), read(v);
    comb(u, v); 
  }
  int Q; read(Q);
  while(Q--){
    char opt; int x, y;
    cin >> opt; 
    read(x), read(y);
    if(opt == 'Q'){
      int fau = find_fa(fa[x]);
      // if(tree.t[fau].w  < y) cout << -1 << endl;
      cout << rev[tree.query_kth(root[fau], 1, n, 1, n, y)] << endl; 
    }
    else{
      comb(x, y); 
    }
  }

  return 0;
}

平衡树合并

依然是上面那道例题,考虑怎么用平衡树写。首先对每个点维护一个平衡树,显然我们需要合并两棵平衡树。
那么思路也非常暴力,选择点数较大的那棵平衡树,然后遍历小的平衡树,将每个点暴力插入至大平衡树。可以证明这样每个点最多只会被插一遍,所以是跑不满的 \(O(logN)\)

例题2 洛谷P3224 [HNOI2012]永无乡

/*
启发式合并:将小平衡树暴力塞入大平衡树中。
本题中每个点最多被合并一次。
*/
#include <bits/stdc++.h>
using namespace std;
#define N 1000010
#define ll long long
const int INF = 1e9; 

template <class T>
inline void read(T& a){
	T x = 0, s = 1;
	char c = getchar();
	while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); }
	a = x * s;
	return ;
}

int n, m; 
int p[N]; 

int root[N]; 

int fa[N]; 
int find_fa(int x){
  return x == fa[x] ? x : fa[x] = find_fa(fa[x]); 
}

struct FHQ_Treap{
  struct node{
    int pri, siz; 
    int ch[2]; 
    int val, id; 
  } t[N]; 

  #define lson ch[0]
  #define rson ch[1]

  int tot; 

  FHQ_Treap(int tot = 0){
    this->tot = tot; 
    return ; 
  }

  int build(int key = 0, int id = 0){
    tot++; 
    t[tot].ch[0] = t[tot].ch[1] = 0;
    t[tot].siz = 1; 
    t[tot].pri = rand(); 
    t[tot].val = key;
    t[tot].id = id; 
    return tot; 
  }

  inline void pushup(int now){
    t[now].siz = t[t[now].lson].siz + t[t[now].rson].siz + 1; 
    return ; 
  }

  void split(int now, int key, int &x, int &y){
    if(!now){
      x = y = 0; 
      return ; 
    }
    if(t[now].val <= key){
      x = now; 
      split(t[now].rson, key, t[now].rson, y); 
      pushup(now); 
    }
    else {
      y = now;
      split(t[now].lson, key, x, t[now].lson);
      pushup(now); 
    }
    return ; 
  }

  int merge(int x, int y){
    if(!x || !y) return x + y;
    if(t[x].pri > t[y].pri){
      t[x].rson = merge(t[x].rson, y); 
      pushup(x);
      return x; 
    }
    else {
      t[y].lson = merge(x, t[y].lson); 
      pushup(y);
      return y; 
    }
  }

  void insert(int &root, int key, int id){
    int x, y;
    split(root, key, x, y); 
    root = merge(merge(x, build(key, id)), y); 
    return ; 
  }

  int find_kth(int now, int k){
    if(!now) return -1; 
    if(t[t[now].lson].siz + 1 == k) return t[now].id; 
    else if(t[t[now].lson].siz >= k) return find_kth(t[now].lson, k); 
    else return find_kth(t[now].rson, k - t[t[now].lson].siz - 1); 
  }

  void link(int u, int now){  // 把小的那棵树直接插入到大的树上
    if(!now) return ; 
    insert(root[u], t[now].val, t[now].id);
    link(u, t[now].lson);
    link(u, t[now].rson);
    return ; 
  }

} tree; 

void comb(int u, int v){
  int fau = find_fa(fa[u]), fav = find_fa(fa[v]);
  if(fau == fav) return ;
  if(tree.t[fau].siz <= tree.t[fav].siz) swap(fau, fav); 
  tree.link(fau, fav);
  fa[fav] = fau;   // 注意合并方向!统一合并到 fau 上(大的那棵树上) 
  return ; 
}

int main(){
  //  freopen("hh.txt", "r", stdin); 
  read(n); read(m); 
  for(int i = 1; i <= n; i++){
    read(p[i]);
    fa[i] = i;  
    tree.insert(root[i], p[i], i); 
  }

  for(int i = 1; i <= m; i++){
    int u, v;
    read(u), read(v); 
    int fau = find_fa(fa[u]), fav = find_fa(fa[v]); 
    comb(u, v);   // 连接 u 和 v 
  }

  int Q; read(Q);
  while(Q--){
    char opt; 
    int x, y;
    cin >> opt; 
    read(x), read(y); 
    if(opt == 'Q'){
      int fau = find_fa(fa[x]);
      // if(tree.t[root[fau]].siz < y) cout << -1 << endl; 
      cout << tree.find_kth(root[fau], y) << endl; 
    }
    else{
      comb(x, y); 
    }
  }
  
  return 0;
}

posted @ 2022-11-27 15:55  雪之下,树之旁  阅读(113)  评论(0编辑  收藏  举报