线段树总结

一、定义与基本描述
线段树(英语:Segment Tree)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。(来自维基百科)
总之线段树就是一个优化区间处理的数据结构,每一个节点都代表着一个区间,而区间中都存放着此区间所需要的数据,然后查询区间数据的时候去查询这些节点所存储的数据即可。
二、基本例题(可以当模板用)
POJ3264 Balanced Lineup
Description
For the daily milking, Farmer John’s N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.
Farmer John has made a list of Q (1 ≤ Q ≤ 200,000) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.
Input
Line 1: Two space-separated integers, N and Q.
Lines 2.. N+1: Line i+1 contains a single integer that is the height of cow i
Lines N+2.. N+ Q+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.
Output
Lines 1.. Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.
Sample Input
6 3
1
7
3
4
2
5
1 5
4 6
2 2
Sample Output
6
3
0
Source
USACO 2007 January Silver
题意:这道题的题意是让你求出所给区间最大值与最小值的差值。
题解:明显的区间问题,用线段树做。先建树,然后依次插入所需数据,每一次插入的时候都更新包含现在插入数据所在区间的值,直到到达叶子节点。查询的时候用区间分解的方法做,因为所要求区间为一个大区间,所以要把大区间分解为节点所代表的小区间,再把每一个小区间的最大最小值相比较即可。这道题的BuildTree等函数都是线段树基本函数,其中Query是区间分解函数,以后写线段树的时候改一下各个函数中更新数据方式或者所需更新的数据即可。

#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <queue>

using namespace std;

struct CNode
{
    int l, r;
    int minh, maxh;
    int Mid()
    {
        return (l + r) / 2;
    }
};

const int maxn = 800010;
const int INF = 1 << 30;
CNode tree[maxn];
int minh, maxh;

void BuildTree(int root, int l, int r)
{
    tree[root].l = l;
    tree[root].r = r;
    tree[root].maxh = -INF;
    tree[root].minh = INF;
    if (l != r) {
        BuildTree (2 * root + 1, l, (l + r) / 2);
        BuildTree (2 * root + 2, (l + r) / 2 + 1, r);
    }
}

void Insert(int root, int i, int v)
{
    if (tree[root].l == tree[root].r) {
        tree[root].minh = tree[root].maxh = v;
        return;
    }
    tree[root].minh = min (tree[root].minh, v);
    tree[root].maxh = max (tree[root].maxh, v);
    if (i <= tree[root].Mid()) {
        Insert (2 * root + 1, i, v);
    } else {
        Insert (2 * root + 2, i, v);
    }
}

void Query(int root, int s, int e)
{
    if (tree[root].minh >= minh && tree[root].maxh <= maxh) return;//这样的话后面的节点就更不用更新了
    if (tree[root].l == s && tree[root].r == e) {//到达区间分解终止区
        minh = min (minh, tree[root].minh);
        maxh = max (maxh, tree[root].maxh);
        return;
    }
    if (e <= tree[root].Mid()) {//继续向下分解,直到分解到终止节点
        Query (root * 2 + 1, s, e);
    } else if (s > tree[root].Mid()) {
        Query (root * 2 + 2, s, e);
    } else {
        Query (root * 2 + 1, s, tree[root].Mid());
        Query (root * 2 + 2, tree[root].Mid() + 1, e);
    }
}

int main()
{
    #ifndef ONLINE_JUDGE
    freopen ("in.txt", "r", stdin);
    #endif // ONLINE_JUDGE
    int n, q;
    scanf ("%d%d", &n, &q);
    BuildTree (0, 1, n);
    for (int i = 1; i <= n; i++) {//i是从1开始,因为0是无意义的叶子节点,叶子节点从1开始。
        int h;
        scanf ("%d", &h);
        Insert (0, i, h);
    }
    while (q--) {
        int l, r;
        scanf ("%d%d", &l, &r);//要用scanf,printf输入输出
        minh = INF;
        maxh = -INF;
        Query (0, l, r);
        int answer = maxh - minh;
        printf ("%d\n", answer);
    }
    return 0;
}

总之,线段树基本题就是要明白要在每一个节点中存什么东西,要如何更新节点中东西,如何查询节点中东西。想好这些了以后套模板就好了。
三、离散化
有时,区间的端点不是整数,或者区间太 大导致建树内存开销过大MLE ,那么就需要 进行“离散化”后再建树。 (来自北京大学课件)
POJ 2528 Mayor’s posters
http://acm.hust.edu.cn/vjudge/problem/14608
题解:先将所给区间离散化,这样就转化为了基本线段树问题。只要一步步区间分解,看是否有地方未被盖住即可。(先从最后贴的开始考虑,这样前面的只需考虑有没有地方未被盖住就好了)。

离散化基本代码段(把原来的端点值转化为新的端点值,区间表示方式转化为新的,更省空间的区间)

    sort (x, x + now);
    int xcount = unique (x, x + now) - x;//unique(num,mun+n)返回的是num去重后的尾地址,好神奇,相当于数组前面元素已经全都不一样了(注意要先排序,因为只删除相邻的相同元素)
    //离散化开始(把现有区间转化,转化完就是基本的线段树问题)
    int nowh = 0;
    for (int i = 0; i < xcount; i++) {
       hash1[x[i]] = nowh;
       if (满足条件) {
           编号++
       }
    }
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <queue>

using namespace std;

struct poster{
    int l, r;
};

struct CNode{
    int l, r;
    bool becovered;
    int Mid(){
        return (l + r) / 2;
    }
};

const int maxn = 40005 * 4;
CNode tree[maxn];
poster pos[maxn];
int x[maxn];
int hash1[10000010];//用哈希表存储离散化以后的各区间边界的值

void BuildTree(int root, int l, int r)
{
    tree[root].l = l;
    tree[root].r = r;
    tree[root].becovered = false;
    if (l != r) {
        BuildTree (2 * root + 1, l, tree[root].Mid());
        BuildTree (2 * root + 2, tree[root].Mid() + 1, r);
    }
}

bool post(int root, int s, int e)
{
    if (tree[root].becovered) return false;
    if (tree[root].l == s && tree[root].r == e)  {
        tree[root].becovered = true;
        //printf ("%d %d\n", s, e);
        return true;
    }
    bool bResult;
    if (e <= tree[root].Mid()) bResult = post (2 * root + 1, s, e);
    else if (s > tree[root].Mid()) bResult = post (2 * root + 2, s, e);
    else {
        bool b1 = post (2 * root + 1, s, tree[root].Mid());//两个都要进行,所以要分别写,不能直接||,如果直接或后面的可能就不会运算
        bool b2 = post (2 * root + 2, tree[root].Mid() + 1, e);
        bResult = b1 || b2;
    }
    if (tree[2 * root + 1].becovered && tree[2 * root + 2].becovered) {//如果下面的两个区间都被完全覆盖,则此区间也被完全覆盖,这个别忘,套路?
        tree[root].becovered = true;
    }
    return bResult;
}

int main()
{
    #ifndef ONLINE_JUDGE
    freopen ("in.txt", "r", stdin);
    #endif // ONLINE_JUDGE
    int t, n, num, now;
    scanf ("%d", &t);
    while (t--) {
        num = 0, now = 0;
        scanf ("%d", &n);
        for (int i = 0; i < n; i++) {
            int l, r;
            scanf ("%d%d", &l, &r);
            x[now++] = l;
            x[now++] = r;
            pos[i].l = l;
            pos[i].r = r;
        }
        sort (x, x + now);
        int xcount = unique (x, x + now) - x;//unique(num,mun+n)返回的是num去重后的尾地址,好神奇,相当于数组前面元素已经全都不一样了(注意要先排序,因为只删除相邻的相同元素)
        //离散化开始(把现有区间转化,转化完就是基本的线段树问题)
        int nowh = 0;
        for (int i = 0; i < xcount; i++) {
            hash1[x[i]] = nowh;
            if (i < xcount - 1) {//如果两个端点中间有区间,则那个区间也要标号,因为那个区间也会关系到一个海报能不能露出
                if (x[i + 1] - x[i] == 1) {
                    nowh++;
                } else {
                    nowh += 2;
                }
            }
        }

        BuildTree (0, 0, nowh);
        for (int i = n - 1; i >= 0; i--) {
            //printf ("x%d y%d\nh%d %d\n", pos[i].l, pos[i].r, hash1[pos[i].l], hash1[pos[i].r]);
            if (post (0, hash1[pos[i].l], hash1[pos[i].r])) {
                num++;
            }
        }
        printf ("%d\n", num);
    }
    return 0;
}

四、扫描线
更新节点数值时从左到右或者从上到下扫描,总之就是把原来所需要更新的操作的顺序有序化,一段一段处理,计算出结果。
POJ 1151 Atlantis
http://acm.hust.edu.cn/vjudge/problem/10336

题解:用一条直线从左到右扫描,碰到一条矩形竖边的时候,就 计算该直线有多长被矩形覆盖,以及被覆盖部分是覆盖了 几重。碰到矩形左边,要增加被覆盖的长度,碰到右边, 要减少被覆盖的长度。每碰到一条矩形的纵边,覆盖面积就增加 Len * 该纵边到下一条纵边的距离。Len是 此时扫描线被矩形覆盖的长度。在Y轴进行离散化。n个矩形的2n个横边纵 坐标共构成最多2n-1个区间的边界,对这 些区间编号,建立起线段树。
插入数据的顺序:
将矩形的纵边从左到右排序,然后依次将这 些纵边插入线段树。要记住哪些纵边是一个 矩形的左边(开始边),哪些纵边是一个矩形 的右边(结束边),以便插入时,对Len和 Covers做不同的修改。
插入一条边后,就根据根节点的Len 值增加总 覆盖面积的值。 增量是Len * 本边到下一条边的距离。(来自北京大学课件)

#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <queue>

using namespace std;

struct Node
{
    int l, r;
    double len;
    int covered;
    int Mid()
    {
        return (l + r) / 2;
    }
};

Node tree[1000];

struct line
{
    double x, y1, y2;
    bool bleft;
} lines[210];

bool operator < (const line &l1, const line &l2)
{
    return l1.x < l2.x;
}

double y[210];

void BuildTree(int root, int l, int r)
{
    tree[root].l = l;
    tree[root].r = r;
    tree[root].covered = 0;
    tree[root].len = 0;
    if (l != r){
        BuildTree (2 * root + 1, l, tree[root].Mid());
        BuildTree (2 * root + 2, tree[root].Mid() + 1, r);
    }
}

template <class F,class T>
F bin_search(F s, F e, T val)//在区间[s,e)中查找 val,找不到就返回 e
{
    F L = s;
    F R = e-1;
    while(L <= R ){
        F mid = L + (R-L)/2;
        if( !( * mid < val  || val < * mid ))      return mid;
        else if(val < * mid)      R = mid - 1;
        else  L = mid + 1;
    }
    return e;
}

void Insert(int root, int s, int e)
{
    if (tree[root].l == s && tree[root].r == e) {
        tree[root].len = y[e + 1] - y[s];
        tree[root].covered++;
        return;
    }
    if (e <= tree[root].Mid()) Insert (2 * root + 1, s, e);
    else if (s > tree[root].Mid()) Insert (2 * root + 2, s, e);
    else {
        Insert (2 * root + 1, s ,tree[root].Mid());
        Insert (2 * root + 2, tree[root].Mid() + 1, e);
    }
    if (tree[root].covered == 0) { //如果不为0,则说明本区间当前仍然被某个矩形完全包含,则不能更新 Len
        tree[root].len = tree[root * 2 + 1].len + tree[root * 2 + 2].len;
    }
}

void Delete(int root, int s, int e)
{
    if (tree[root].l == s && tree[root].r == e) {
        tree[root].covered--;
        if (tree[root].covered == 0) {
            if (tree[root].l == tree[root].r) {//如果不特殊讨论,会超限
                tree[root].len = 0;
            } else {
                tree[root].len = tree[root * 2 + 1].len + tree[root * 2 + 2].len;
            }
        }
        return;
    }
    if (e <= tree[root].Mid()) Delete (2 * root + 1, s, e);
    else if (s > tree[root].Mid()) Delete (2 * root + 2, s, e);
    else {
        Delete (2 * root + 1, s ,tree[root].Mid());
        Delete (2 * root + 2, tree[root].Mid() + 1, e);
    }
    if (tree[root].covered == 0) { //如果不为0,则说明本区间当前仍然被某个矩形完全包含,则不能更新 Len
        tree[root].len = tree[root * 2 + 1].len + tree[root * 2 + 2].len;
    }
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen ("in.txt", "r", stdin);
#endif // ONLINE_JUDGE
    int n, Case = 1;
    while (1){
        scanf ("%d", &n);
        if (n == 0) break;
        printf ("Test case #%d\n", Case++);
        double x1, x2, y1, y2;
        int yc = 0;
        int lc = 0;//lc存储的是竖边的个数+1
        for (int i = 0; i < n; i++){
            scanf ("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            y[yc++] = y1;
            y[yc++] = y2;
            lines[lc].x = x1;
            lines[lc].y1 = y1;
            lines[lc].y2 = y2;
            lines[lc].bleft = true;
            lc++;
            lines[lc].x = x2;
            lines[lc].y1 = y1;
            lines[lc].y2 = y2;
            lines[lc].bleft = false;
            lc++;
        }

        //离散化后建树
        sort (y, y + yc);
        yc = unique (y, y + yc) - y;//yc是所有不同y值的数量,所以区间数为yc - 1
        BuildTree (0, 0, yc - 1 - 1);

        //插入或者删除边并计算面积
        sort (lines, lines + lc);//因为扫描线是从左到右扫描,所以要给边排序
        double Area = 0;
        for (int i = 0; i < lc - 1; i++){
            //要找到当前y1,y2在y中的哪个位置,也就是确定要插入或者删除区间的端点
            int l = bin_search (y, y + yc, lines[i].y1) - y;
            int r = bin_search (y, y + yc, lines[i].y2) - y;
            if (lines[i].bleft) {
                Insert (0, l, r - 1);
            } else {
                Delete (0, l, r - 1);
            }
            Area += tree[0].len * (lines[i + 1].x - lines[i].x);
        }

        printf("Total explored area: %.2f\n\n", Area);
    }
    return 0;
}

总之,扫描线就是要把更新顺序变得更有序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值