线段树合并与平衡树合并算法思路
线段树合并
线段树合并,听起来很高端,其实就是把两棵线段树相加。
引用一下一位大佬的图:
具体地说,每次合并操作我们考虑将 \(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;
}
。