线段树的基本概念可以参照下: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建成的线段树如下图:
我们给每个区间设置了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
这些题都有涉及到区间连续合并等。