线段树之区间合并(HDU-3380)
线段树的区间合并一般是用来解决形如 ”区间连续子序列“ 的问题,并且是 ”可修改的“ 区间,比如说是下面的这道例题:HDU-3380 LCIS,求的是区间最长连续上升子序列。
如果使用结点式的线段树,那么要进行区间合并的话一般是这样定义的:
struct Node {
int left, right;
int cntLeft, cntRight, cntMax;
} tree[4*maxn];
我们需要维护三个值,
左端点开始的最长连续子序列长度 —— cntLeft,
右端点开始的最长连续子序列长度 —— cntRight,
区间 [left, right] 中的最长连续子序列长度 —— cntMax。
其中前两个比较好理解,需要讲的是 cntMax,我们使用线段树的区间合并就是为了求区间的最长连续子序列,那么 cntMax 就是我们需要求的,那么它该如何确定呢?
对于叶子结点来说,只有一个点,那么 cntLeft、cntRight 和 cntMax 都是 1;
对于其他结点来说,这个区间的 cntMax 可能等于左右区间中较大的 cntMax,但是在存在着最长连续子序列穿过中点的情况,即左区间的部分+右区间的部分是 cntMax 的情况。
cntMax = max(左区间的 cntMax, 右区间的cntMax, 左区间的 cntRight + 右区间的 cntLeft);
除此以外,我们还需要注意 cntLeft 和 cntRight 的确定,
如果左区间的 cntLeft 是整个区间,那么当前区间的 cntLeft = 左区间的 cntLeft + 右区间的 cntLeft;
如果右区间的 cntRight 是整个区间,那么当前区间的 cntRight = 右区间的 cntRight + 左区间的 cntRight;
tree[k].cntLeft = tree[2*k].cntLeft;
tree[k].cntRight = tree[2*k+1].cntRight;
if(tree[2*k].cntLeft == tree[2*k].right-tree[2*k].left+1) {
tree[k].cntLeft += tree[2*k+1].cntLeft;
}
if(tree[2*k+1].cntRight == tree[2*k+1].right-tree[2*k+1].left+1) {
tree[k].cntRight += tree[2*k].cntRight;
}
那么这样确定策略之后我们就可以开始解决问题了。
一道简单题目:LCIS HDU - 3308
题目大意:给定一个长度为 n 的序列以及 m 个操作,一共有两种操作:
U A B:表示将第 A 个数字被替换为 B。(从0开始计数)(0 <= A < n, 0 <= B <= 1e5)
Q A B:表示查询区间 [A, B] 中的最长连续递增子序列的长度。(0 <= A <= B < n)
(0 < n, m <= 1e5)
sample input:
1
10 10
7 7 3 3 5 9 9 8 1 8
Q 6 6
U 3 4
Q 0 1
Q 0 5
Q 4 7
Q 3 5
Q 0 2
Q 4 6
U 6 10
Q 0 9
sample output:
1
1
4
2
3
1
2
5
几乎就是一个线段树区间合并的裸题,只要注意 pushUp 和 ask_interval 两个操作即可,详见代码。
// 线段树 区间合并
#include<iostream>
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn = 1e5+10;
int n, m, ans;
int arr[maxn];
struct Node {
int left, right;
// 分别为 左连续 右连续 以及 最长连续
int cntLeft, cntRight, cntMax;
} tree[4*maxn];
void pushUp(int k) {
if(tree[k].left == tree[k].right) return;
int mid = (tree[k].left+tree[k].right)/2;
// 默认为左孩子的左连续
tree[k].cntLeft = tree[2*k].cntLeft;
// 默认为右孩子的右连续
tree[k].cntRight = tree[2*k+1].cntRight;
// 默认为两个孩子中较大的最长连续
tree[k].cntMax = max(tree[2*k].cntMax, tree[2*k+1].cntMax);
// 若左右孩子可以连接在一起
if(arr[mid] < arr[mid+1]) {
// 若左孩子整个都是连续的,则要加上右孩子的左连续
if(tree[2*k].cntLeft == tree[2*k].right-tree[2*k].left+1) {
tree[k].cntLeft += tree[2*k+1].cntLeft;
}
// 若右孩子整个都是连续的,则要加上左孩子的右连续
if(tree[2*k+1].cntRight == tree[2*k+1].right-tree[2*k+1].left+1) {
tree[k].cntRight += tree[2*k].cntRight;
}
// 最长连续可能是左孩子的右连续+右孩子的左连续
tree[k].cntMax = max(tree[k].cntMax, tree[2*k].cntRight+tree[2*k+1].cntLeft);
}
}
void build(int k, int l, int r) {
tree[k].left = l;
tree[k].right = r;
if(l == r) {
// 只有一个元素,因此为 1
tree[k].cntLeft = tree[k].cntRight = tree[k].cntMax = 1;
return;
}
int mid = (l+r)/2;
build(2*k, l, mid);
build(2*k+1, mid+1, r);
pushUp(k);
}
void change_point(int k, int x, int c) {
if(tree[k].left == tree[k].right) {
arr[x] = c;
return;
}
int mid = (tree[k].left+tree[k].right)/2;
if(x <= mid) change_point(2*k, x, c);
else change_point(2*k+1, x, c);
pushUp(k);
}
void ask_interval(int k, int l, int r) {
if(tree[k].left >= l && tree[k].right <= r){
ans = max(ans, tree[k].cntMax);
return;
}
int mid = (tree[k].left+tree[k].right)/2;
if(l <= mid) ask_interval(2*k, l, r);
if(mid < r) ask_interval(2*k+1, l , r);
// 若该区间经过中点,则要判断是否左孩子的右连续+右孩子的左连续更大
if(mid >= l && mid <= r && arr[mid] < arr[mid+1]){
ans = max(ans, min(mid-l+1, tree[2*k].cntRight)+min(r-mid, tree[2*k+1].cntLeft));
}
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
build(1, 0, n-1);
for(int i = 0; i < m; i++) {
char ch[2];
scanf("%s", ch);
int l, r;
scanf("%d%d", &l, &r);
if(ch[0] == 'Q') {
ans = 0;
ask_interval(1, l, r);
printf("%d\n", ans);
} else {
change_point(1, l, r);
}
}
}
}
【END】感谢观看!