线段树之区间合并(HDU-3380)

本文深入探讨线段树的区间合并技术,用于求解区间最长连续子序列问题,通过HDU-3380 LCIS题例,解析结点式线段树的维护策略,包括cntLeft、cntRight及cntMax的更新逻辑,最后提供LCISHDU-3308题目的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线段树之区间合并(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】感谢观看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值