线段树分裂合并

线段树分裂合并

我先接触的是线段树合并所以先讲线段树合并。

首先,用来合并的线段树必须是动态开点的。线段树合并所做的事就是合并两棵动态开点线段树的信息,对于两棵动态开点线段树,可能会存在一些公共节点,我们所要做的就是合并这些节点的信息,然后把其他节点的信息继承。理清思路之后,剩下的事就是。设初始信息的个数是\(n\),值域是\(m\),对于每一个初始信息一次这样的操作是\(\log m\)的,而每个信息只会被合并一次,所以一般的线段树合并是\(O(n \log m)\)的。非常好理解,直接上模板题:[POI2011]ROT-Tree Rotations

这题就是对每一个叶节点点建一棵权值线段树;对于每个非叶节点,先计算左右儿子交换前后的逆序对数取较小值加入答案,再合并左右儿子的线段树成为这个结点的线段树。用权值线段树的目的是快速求逆序对,线段树合并的目的是快速合并左右儿子信息。

//written by newbiechd
#include <cstdio>
#include <cctype>
#define R register
#define I inline
#define B 1000000
#define L long long
using namespace std;
const int N = 200003;
char buf[B], *p1, *p2;
I char gc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, B, stdin), p1 == p2) ? EOF : *p1++; }
I int rd() {
    R int f = 0;
    R char c = gc();
    while (c < 48 || c > 57)
        c = gc();
    while (c > 47 && c < 58)
        f = f * 10 + (c ^ 48), c = gc();
    return f;
}
int rt[N], n, E, T;
L a, b, ans;
struct segtree { int v, p, q; }e[N << 5];
I L min(L x, L y) { return x < y ? x : y; }
void insert(int &k, int l, int r, int x) {
    if (!k)
        k = ++T;
    ++e[k].v;
    if (l == r)
        return ;
    R int m = l + r >> 1;
    if (x <= m)
        insert(e[k].p, l, m, x);
    else
        insert(e[k].q, m + 1, r, x);
}
int merge(int k, int t, int l, int r) {
    if (!k)
        return t;
    if (!t)
        return k;
    e[k].v += e[t].v;
    if (l == r)
        return k;
    R int m = l + r >> 1;
    a += 1ll * e[e[k].q].v * e[e[t].p].v, b += 1ll * e[e[t].q].v * e[e[k].p].v;
    e[k].p = merge(e[k].p, e[t].p, l, m), e[k].q = merge(e[k].q, e[t].q, m + 1, r);
    return k;
}
void solve(int &k) {
    R int x = rd();
    if (x) {
        k = ++E, insert(rt[E], 1, n, x);
        return ;
    }
    R int p, q;
    solve(p), solve(q), a = b = 0, merge(rt[p], rt[q], 1, n), ans += min(a, b), k = p;
}
int main() {
    R int k;
    n = rd(), solve(k), printf("%lld", ans);
    return 0;
}

放几道习题:

[Vani有约会]雨天的尾巴 题解(我当时貌似在这篇题解里又把动态开点线段树和线段树合并讲了一遍)

[NOIp2016]天天爱跑步 题解

[BJOI2017]树的难题 题解

有时候,我们不仅需要把多棵线段树的信息合并,在有些情况下还需要把一颗线段树的信息分裂成几棵,这时候就要用到线段树分裂了。下面讲讲线段树分裂。

在形式上,线段树分裂可以看作线段树合并的逆操作,事实上线段树分裂并不是还原线段树合并之前的信息,结合下面的例题理解:[HEOI2016/TJOI2016]排序,这题是要求多次取一个全排列的一段子串升序或降序排序,最后求一个位置的权值。

有一种二分答案然后把原问题转化为01序列排序的方法,复杂度\(O(n (log n) ^ 2)\),这里不做过多介绍。

线段树分裂的方法可以支持最后查询一个区间,下面就按照最后查询\(l\)到\(r\)的一个区间来介绍。

首先考虑对一个排列可以桶排序,我们想象每次把题目要修改的位置的权值全部放到桶中就完成了排序,假设一开始每个位置有一个桶,放的是这个位置的权值:这时要取出一段区间排序,就要想办法把这段区间的桶合并成一个,顺序/倒序遍历的结果就是升序/降序排列的结果;有时候需要排序的区间的端点在某些已经排好序了的区间中,即现在需要排序的某些元素在有其他元素的桶中,这时就要我们想办法取出这些元素,即把这个桶分裂;最后查询的时候,我们就取出\(l\)到\(r\)这段区间(这道题就是\(q\)这个点)内的桶合并,同样的,对于含有端点的桶,如果还含有不在这段区间内的元素,就要把这个桶分裂。

我们觉得桶没法快速支持合并和分裂操作,所以我们就用动态开点的权值线段树来实现。其实把上面这段话中的“桶”全部换成“权值线段树”这道题就做完了。

我们已经会线段树合并了,但是还不会线段树分裂。现在看看这题,事实上我们只需要取出一段已经排好序的序列的前\(k\)大/前\(k\)小,并把这些部分放到一棵新的权值线段树上。那么我们直接在线段树上做一个取出前\(k\)大/前\(k\)小的操作就行了,唯一的不同就是把这些部分取出来时另开一颗线段树记录这些信息,想法非常自然,可以结合代码理解。

什么?你不知道怎么判断一个点是不是在已经排好序的区间里?

那你可能要向全国的珂学家谢罪了:直接开个set,里面的结点是区间,剩下的不用我说了吧。

具体实现的时候写了个内存回收。

//written by newbiechd
#include <cstdio>
#include <cctype>
#include <set>
#define R register
#define I inline
#define B 1000000
using namespace std;
const int N = 100003;
char buf[B], *p1, *p2;
I char gc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, B, stdin), p1 == p2) ? EOF : *p1++; }
I int rd() {
    R int f = 0;
    R char c = gc();
    while (c < 48 || c > 57)
        c = gc();
    while (c > 47 && c < 58)
        f = f * 10 + (c ^ 48), c = gc();
    return f;
}
int sta[N << 5], top, n;
struct segtree { int p, q, s; }e[N << 5];
struct node { int l, r, k, t; };
set <node> s;
I int operator < (node x, node y) { return x.r ^ y.r ? x.r < y.r : x.l < y.l; }
I int newnode() {
    R int t = sta[top--];
    e[t] = (segtree){0, 0, 0};
    return t;
}
void insert(int &k, int l, int r, int x) {
    if (!k)
        k = newnode();
    ++e[k].s;
    if (l == r)
        return ;
    R int m = l + r >> 1;
    if (x <= m)
        insert(e[k].p, l, m, x);
    else
        insert(e[k].q, m + 1, r, x);
}
void split(int &k, int t, int l, int r, int x) {
    if (!x)
        return ;
    if (!k)
        k = newnode();
    e[k].s += x, e[t].s -= x;
    if (l == r)
        return ;
    R int m = l + r >> 1, y = e[e[t].p].s;
    if (x < y)
        split(e[k].p, e[t].p, l, m, x);
    else {
        e[k].p = e[t].p, e[t].p = 0;
        split(e[k].q, e[t].q, m + 1, r, x - y);
    }
}
int merge(int k, int t) {
    if (!k || !t)
        return k | t;
    e[k].p = merge(e[k].p, e[t].p), e[k].q = merge(e[k].q, e[t].q);
    e[k].s += e[t].s, sta[++top] = t;
    return k;
}
int kth(int k, int l, int r, int x) {
    if (l == r)
        return l;
    R int m = l + r >> 1;
    if (e[e[k].p].s >= x)
        return kth(e[k].p, l, m, x);
    else
        return kth(e[k].q, m + 1, r, x - e[e[k].p].s);
}
I int nsplit(int l, int r) {
    set <node>::iterator it = s.lower_bound((node){0, l, 0, 0});
    if ((*it).l ^ l) {
        node t = *it;
        R int k = 0;
        s.erase(it);
        if (t.t) {
            split(k, t.k, 1, n, t.r - l + 1);
            s.insert((node){t.l, l - 1, t.k, 1});
            s.insert((node){l, t.r, k, 1});
        }
        else {
            split(k, t.k, 1, n, l - t.l);
            s.insert((node){t.l, l - 1, k, 0});
            s.insert((node){l, t.r, t.k, 0});
        }
    }
    it = s.lower_bound((node){0, r, 0, 0});
    if ((*it).r ^ r) {
        node t = *it;
        R int k = 0;
        s.erase(it);
        if (t.t) {
            split(k, t.k, 1, n, t.r - r);
            s.insert((node){t.l, r, t.k, 1});
            s.insert((node){r + 1, t.r, k, 1});
        }
        else {
            split(k, t.k, 1, n, r - t.l + 1);
            s.insert((node){t.l, r, k, 0});
            s.insert((node){r + 1, t.r, t.k, 0});
        }
    }
    R int o = 0;
    while (1) {
        it = s.lower_bound((node){0, l, 0, 0});
        if (it == s.end() || (*it).l > r)
            break;
        node t = *it;
        s.erase(it);
        o = merge(o, t.k);
    }
    return o;
}
int main() {
    R int m, i, x, y, opt, Q;
    n = rd(), m = rd();
    for (i = (N << 5) - 1; i; --i)
        sta[++top] = i;
    for (i = 1; i <= n; ++i)
        x = rd(), y = 0, insert(y, 1, n, x), s.insert((node){i, i, y, 0});
    while (m--) {
        opt = rd(), x = rd(), y = rd(), i = nsplit(x, y);
        s.insert((node){x, y, i, opt});
    }
    Q = rd(), i = nsplit(Q, Q), printf("%d", kth(i, 1, n, 1));
    return 0;
}

原文地址:https://www.cnblogs.com/cj-chd/p/10354286.html

时间: 2024-10-06 09:36:15

线段树分裂合并的相关文章

HDU - 1540 线段树的合并

这个题题意我大概解释一下,就是一开始一条直线,上面的点全是联通的,有三种操作 1.操作D把从左往右第x个村庄摧毁,然后断开两边的联通. 2.询问Q节点相联通的最长长度 3.把最后破坏的村庄重建. 这个其实也是非常典型的线段树区间合并,正好可以学一下. 我们给线段树的结点赋予5个值,l 区间左端点, r 区间右端点, ls从左端点开始的最大连续个数(左连续),rs从右端点开始最大的连续个数,ms这个节点所表示的区间内部,最大的连续个数. 然后我们考虑建树,由于最开始是相通的,因此这些值初始为r-l

P6012 【模板】线段树分裂

(因为没有认证,所以这道题就由Froggy上传) 线段树分裂用到的地方确实并不多,luogu上以前也没有这道模板题,所以就出了一道,实在是想不出怎么出模板了,所以这道题可能可以用一些其他的算法水过去. 前置芝士 线段树: 本题中用到的是权值线段树(查询每个数在序列中出现的次数,序列中第k大的数等操作). 线段树合并: 为了增加一下码量才放上的. 线段树分裂 既然是模板题,这个自然才是重点. 以下这样一颗线段树: 需要将橙色线段覆盖的部分分裂出来,需要建一颗新的树,当一个节点所代表的区间与需要分裂

HDU 3911 Black And White(线段树区间合并)

Problem Description There are a bunch of stones on the beach; Stone color is white or black. Little Sheep has a magic brush, she can change the color of a continuous stone, black to white, white to black. Little Sheep like black very much, so she wan

HDU 3308 LCIS (线段树区间合并)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3308 题目很好懂,就是单点更新,然后求区间的最长上升子序列. 线段树区间合并问题,注意合并的条件是a[mid + 1] > a[mid],写的细心点就好了. 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 const int MAXN = 1

HDU 5316 Magician(线段树区间合并入门)

本文纯属原创,转载请注明出处谢谢.http://blog.csdn.net/zip_fan. 题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5316 Time Limit: 18000/9000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Problem Description Fantasy magicians usually gain their ability

SPOJ COT3 Combat on a tree(Trie树、线段树的合并)

题目链接:http://www.spoj.com/problems/COT3/ Alice and Bob are playing a game on a tree of n nodes.Each node is either black or white initially. They take turns to do the following operation:Choose a white node v from the current tree;Color all white node

poj3667 线段树 区间合并

1 //Accepted 3728 KB 1079 ms 2 //线段树 区间合并 3 #include <cstdio> 4 #include <cstring> 5 #include <iostream> 6 #include <queue> 7 #include <cmath> 8 #include <algorithm> 9 using namespace std; 10 /** 11 * This is a document

hdu3911 线段树 区间合并

1 //Accepted 3911 750MS 9872K 2 //线段树 区间合并 3 #include <cstdio> 4 #include <cstring> 5 #include <iostream> 6 #include <queue> 7 #include <cmath> 8 #include <algorithm> 9 using namespace std; 10 /** 11 * This is a documen

线段树 区间合并

poj3667 Hotel 区间合并入门题,照着代码打的, 题意:1 a:询问是不是有连续长度为a的空房间,有的话住进最左边       2 a b:将[a,a+b-1]的房间清空思路:记录区间中最长的空房间,开三个数组,msum[rt]表示节点rt内连续的1的个数的最大值,lsum[rt]表示从节点rt左端点开始连续1的个数,rsum[rt]表示从节点rt右端点开始连续1的个数..线段树操作:update:区间替换 query:询问满足条件的最左端点 1 #include<iostream>