[置顶] 线段树-区间合并

线段树的基本概念可以参照下:http://blog.csdn.net/metalseed/article/details/8039326

现在我们来说说区间合并。

        先来一道例题:HDU 3308

        题意:给你n(n<=100000)个数,再给你m个操作。操作分为两种。第一种:'U' A B 将A这个位置上的数置为B;第二种:'Q' A B 询问[A,B]区间最长上升子序列。(数组下标从0开始)

        例如给出一组数据:

INPUT:

10 4

7 7 3 3 5 9 9 8 1 8

Q 0 9

U 2 2

U 6 11

Q 0 9

OUTPUT:

3                                       

5

       数据解释:1、[0,9]这个区间最长的上升子序列是3 5 9 所以输出的答案是3。2、进过U 2 2 、U 6 11的操作 得到的序列为 7 7 2 3 5 9 11 8 1 8。3、[0,9]这个区间的最长上升子序列则为2 3 5 9 11 所以输出的答案为5。

       题解:我们先来分析分析这题,单点更新,区间询问(数组dis[N]来表示)。平时我们写的线段树的区间求和的时候是直接区间进行相加,因为求和并没有限制。但是呢,在这里并不能直接去合并,因为有一个限制:“上升”。我们这样来分析下。给出两个区间:[a,b]、[b+1,c]。这个区间在询问时合并的条件是什么?dis[b] < dis[b+1]。这里就有了限制了,在这里我们改如何进行查询?线段树的特点在于什么,就是能仅凭O(log(n))的时间来进行一个区间的操作,它操作的是一个区间。

      我们来看看线段树的节点该设置一些什么变量。

      例如:7 3 1 4 5 6 9 11 7建成的线段树如下图:

=[置顶] 线段树-区间合并_第1张图片


我们给每个区间设置了5个变量,在图的右上角。

我们在线段树上的叶子节点进行初始化(如lr这个节点的值为v):ans[lr] = lans[lr] = rans[lr] = 1, rid[lr] = lid[lr] = v。

因为这里只是端点更新,所以我们只需用到pushup(),不必用到pushdown()。

1、我们来看看如何进行pushup()。

如下图:


所谓的pushup()是将儿子的节点反馈会父亲节点,用两个儿子节点的信息把父亲节点的信息给更新掉。

我们假设父亲节点为lr,长度为m。

lr<<1:左儿子

lr<<1|1:右儿子

来看看pushup所要进行的操作。


我们先看这两个代码:

lans[lr] = lans[lr<<1];

rans[lr] = rans[lr<<1|1];

先把左儿子的lans给了父亲的lans,右儿子的rans给了父亲rans。这里比较好理解吧。


我们再看下一个代码:

ans[lr] = max(ans[lr<<1],ans[lr<<1|1]);

这里比较好理解,就不多说了。


我们继续:

if(rid[lr<<1]<lid[lr<<1|1])

{

if(lans[lr] == (m-(m>>1))) lans[lr] += lans[lr<<1|1];

if(rans[lr] == (m >>1)) rans[lr] += rans[lr<<1];

}

这里就有了区间合并。

我们看图中rans[5] = 2;且1<4所以lr的右连续包含了左儿子的点,所以rans[lr] += rans[lr<<1];

所以就是如此。

再看下一个:

if(rid[lr<<1]<lid[lr<<1|1])

{

    ans[lr] = max(ans[lr],rans[lr<<1]+lans[lr<<1|1]);

}

这个地方就更新父亲的最大值。懂了上面这个,这个也就理解了。

2、query();函数。

询问最长上升子序列的时候我们先看代码。

int query(int ll, int rr, int lr, int l, int r) {
    if(ll <= l && r <= rr) {
        return ans[lr];
    }
    int vc = 0;
    int mid = (l + r) >> 1;
    if(rr <= mid) return query(ll, rr, lson);
    if(ll > mid) return query(ll, rr, rson);
    int a = min(rans[lr << 1], mid - ll + 1);
    int b = min(lans[lr << 1 | 1], rr - mid);
    if(rid[lr << 1] < lid[lr << 1 | 1]) {
        return max(a + b, max(query(ll, rr, lson), query(ll, rr, rson)));
    }
    return max(query(ll, rr, lson), query(ll, rr, rson));;
}
我们来看看查询函数query();

其实这里比较难理解的地方在于a,b。

我们可以这样理解。

如下图:

a与b的出现在于当我询问[1,4]这个区间的时候。

[1,2] 与[3,4]并不在一起,所以我们要进行合并,左右两边都要。

但是在于区间的大小肯定不能超过询问的大小,所以进行:

    int a = min(rans[lr << 1], mid - ll + 1);
    int b = min(lans[lr << 1 | 1], rr - mid);



这题就这样做完了。

我们来看下这题的代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<vector>
#include<bitset>
#include<set>
#include<queue>
#include<stack>
#include<map>
#include<cstdlib>
#include<cmath>
#define PI 2*asin(1.0)
#define LL __int64
#define pb push_back
#define clr(a,b) memset(a,b,sizeof(a))
#define lson lr<<1,l,mid
#define rson lr<<1|1,mid+1,r
const int  MOD = 1e9 + 7;
const int N = 1e5 + 15;
const int INF = (1 << 30) - 1;
const int letter = 130;
using namespace std;
int n, q;
int ans[N << 2], lans[N << 2], rans[N << 2], lid[N<<2], rid[N<<2];
int mark[N << 2];
void pushup(int lr, int m) {
    lid[lr] = lid[lr << 1];
    rid[lr] = rid[lr << 1 | 1];
    lans[lr] = lans[lr << 1];
    rans[lr] = rans[lr << 1 | 1];
    ans[lr] = max(ans[lr << 1], ans[lr << 1 | 1]);
    if(rid[lr << 1] < lid[lr << 1 | 1]) {
        int v = rans[lr << 1] + lans[lr << 1 | 1];
        if(lans[lr] == (m - ( m >> 1))) lans[lr] += lans[lr << 1 | 1];
        if(rans[lr] == (m >> 1) ) rans[lr] += rans[lr << 1];
        ans[lr] = max(v, ans[lr]);
    }
}
void build(int lr, int l, int r) {
    int mid = (l + r) >> 1;
    if(l == r) {
        ans[lr] = lans[lr] = rans[lr] = 1;
        scanf("%d", &lid[lr]);
        rid[lr] = lid[lr];
        return;
    }
    build(lson);
    build(rson);
    pushup(lr, r - l + 1);
}
void update(int id, int v, int lr, int l, int r) {
    if(l == r) {
        lid[lr] = rid[lr] = v;
        return;
    }
    int mid = (l + r) >> 1;
    if(id <= mid) update(id, v, lson);
    else if(id > mid) update(id, v, rson);
    pushup(lr, r - l + 1);
}
int query(int ll, int rr, int lr, int l, int r) {
    if(ll <= l && r <= rr) {
        return ans[lr];
    }
    int vc = 0;
    int mid = (l + r) >> 1;
    if(rr <= mid) return query(ll, rr, lson);
    if(ll > mid) return query(ll, rr, rson);
    int a = min(rans[lr << 1], mid - ll + 1);
    int b = min(lans[lr << 1 | 1], rr - mid);
    if(rid[lr << 1] < lid[lr << 1 | 1]) {
        return max(a + b, max(query(ll, rr, lson), query(ll, rr, rson)));
    }
    return max(query(ll, rr, lson), query(ll, rr, rson));;
}
int main() {
    int tc;
    scanf("%d", &tc);
    while(tc--) {
        scanf("%d%d", &n, &q);
        clr(ans, 0);
        clr(lans, 0);
        clr(rans, 0);
        clr(lid, 0);
        clr(rid, 0);
        build(1, 1, n);
        char str[2];
        int a, b;
        while(q--) {
            scanf("%s%d%d", str, &a, &b);
            if(str[0] == 'Q') {
                printf("%d\n", query(a + 1, b + 1, 1, 1, n));
            } else {
                update(a + 1, b, 1, 1, n);
            }
        }
    }
    return 0;
}

总结:对于这类题我们要分析区间里面要存什么元素,并且合并的时候有什么要注意的,合并的条件是什么。区间左右端点是否有用等。

再来几题推荐题目:

1、http://acm.hdu.edu.cn/showproblem.php?pid=3911

2、http://poj.org/problem?id=3667

这些题都有涉及到区间连续合并等。

你可能感兴趣的:(数据结构,线段树)