• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
jacklee404
Never Stop!
博客园    首页    新随笔    联系   管理    订阅  订阅
线段树复习

线段树复习

​ 线段树是基于分治思想的二叉树,用来维护区间信息(区间和,区间最值,区间\(gcd\)等),可以在\(nlogn\)的时间内执行区间修改和区间查询 。

​ 线段树的叶子节点存储元素本身,非叶子节点存储区间内的元素统计值。

建树

关于线段树为什么开\(4 \times N\)大小:

image-20230318195215752

单点修改 \(O(logn)\)

​ 从根节点进入,递归找到叶子节点\([x, x]\),对该节点增加\(k\),然后从下往上进入祖先节点进行更新

区间查询 \(O(logn)\)

​ 利用拆分与拼凑的思想, 例如区间\([5, 7]\), 可以被拆分成\([5, 5],[6, 8],[9, 9]\),通过合并三个区间的答案求得查询答案。

​ 从根节点进入,递归执行一下过程:

  1. 若查询区间\([x, y]\) 完全覆盖当前节点的区间,则立即回溯,并返回该节点的sum值
  2. 若当前节点的左子树节点与\([x, y]\)有重叠,则递归访问左子树
  3. 若当前节点的右子树节点与\([x, y]\)有重叠,则递归访问右子树
int query(int p, int x, int y) {
    if (x <= tr[p].l && y <= tr[p].r)
        return tr[p].sum;
    int m = tr[p].l + tr[p].r >> 1, sum = 0;
    if (x <= m) sum += query(lc, x, y);
    if (y >= m)	sum += query(rc, x, y);
    return sum;
}

区间修改 \(O(logn)\)-懒标记

​ 如果对于区间\([1, n]\)进行修改,我们需要修改叶子节点,然后更改祖宗节点的值,那么复杂度将会是\(O(n)\)的,我们需要换个思路。

​ 我们做懒惰修改,如果当前区间完全覆盖\([x, y]\), 当\([x, y]\)完全覆盖节点区间\([ a, b]\)时,先修改区间的\(sum\)值,在打上一个懒标记, 然后立即返回,当下次需要的时候,在下传懒标记,这样可以把每次修改和查询的时间都控制在\(O(logn)\)(复杂度与查询相同)

模板

#define lc p<<1
#define rc p<<1|1

const int N = 1e5 + 10;

using i64 = long long;

int n, w[N];

struct node {
	i64 l, r, sum, add;
}tr[4 * N];

void pushdown(int p) {
	if (tr[p].add) {
		tr[lc].sum += (tr[lc].r - tr[lc].l + 1) * tr[p].add;
		tr[rc].sum += (tr[rc].r - tr[rc].l + 1) * tr[p].add;
		tr[lc].add += tr[p].add;
		tr[rc].add += tr[p].add;
		tr[p].add = 0;
	}
}

void pushup(int p) {
	tr[p].sum = tr[lc].sum + tr[rc].sum;
}

void build(int p, int l, int r) {
	tr[p] = {l, r, w[l], 0};

	if(l == r) return;

	int m = l + r >> 1;

	build(lc, l, m);
	build(rc, m + 1, r);

	pushup(p);
}

i64 query(int p, int x, int y) {
	if (tr[p].l >= x && tr[p].r <= y) {
		return tr[p].sum;
	}

	int m = tr[p].l + tr[p].r >> 1;
	i64 sum = 0;

	pushdown(p);

	if (x <= m) sum += query(lc, x, y);

	if (y > m) sum += query(rc, x, y);

	return sum;
}

void update(int p, int x, int y, int k) {
	if (tr[p].l >= x && tr[p].r <= y) {
		tr[p].sum += (tr[p].r - tr[p].l + 1) * k;
		tr[p].add += k;
		return;
	}

	int m = tr[p].l + tr[p].r >> 1;

	pushdown(p);

	if (x <= m) update(lc, x, y, k);

	if (y > m) update(rc, x, y, k);

	pushup(p);
}

线段树应用

区间 gcd

在更相损减法的\(n\)个推广情况下:\(gcd(a_1, a_2, \dots, a_n) = gcd(a_1, a_2 - a_1, \dots a_n - a_{n - 1})\), 显然右侧是一个差分序列

所以\(gcd[l, r] = gcd(a_l, gcd[b_{l + 1}, b_r])\)

\(差分 \rightarrow(求前缀和)\rightarrow 原序列\)

\(原序列 \rightarrow(区间修改[l,r])\rightarrow差分序列单点修改\)

​ 现在我们只需要维护一个单点查询,和单点修改(其实单点修改完还要求前缀, 可以直接上树状数组)。

当然一个线段树也可以维护

#include <iostream>
#define lc p<<1
#define rc p<<1|1
using i64 = long long;

const int N = 5e5 + 10;

struct node {
	int l, r;
	i64 sum, b;
} tr[4 * N];

i64 w[N];

i64 gcd(i64 a, i64 b) {
	return b?gcd(b, a % b):a;
}

void pushup(int p) {
	tr[p].sum = tr[lc].sum + tr[rc].sum;
	tr[p].b = gcd(tr[lc].b, tr[rc].b);
}

void build(int p, int l, int r) {
	tr[p] = {l, r, w[r] - w[r - 1], w[r] - w[r - 1]};
	if (l == r) return;
	int m = l + r >> 1;
	build(lc, l, m);
	build(rc, m + 1, r);
	pushup(p);
}

void update(int p, int x, int y, i64 k) {
	if (tr[p].l >= x && tr[p].r <= y) {
		tr[p].b += k;
		tr[p].sum += k;
		return;
	}

	int m = tr[p].l + tr[p].r >> 1;

	if (x <= m) update(lc, x, y, k);
	if (y > m) update(rc, x, y, k);

	pushup(p);
}

i64 query(int p, int x, int y) {
	if (tr[p].l >= x && tr[p].r <= y) {
		return tr[p].b;
	}

	i64 res = 0;

	int m = tr[p].l + tr[p].r >> 1;

	if (x <= m) res = gcd(res, query(lc, x, y));
	if (y > m) res = gcd(res, query(rc, x, y));

	return res;
}

i64 query2(int p, int x, int y) {
	if (tr[p].l >= x && tr[p].r <= y) {
		return tr[p].sum;
	}

	i64 res = 0;

	int m = tr[p].l + tr[p].r >> 1;

	if (x <= m) res += query2(lc, x, y);
	if (y > m) res += query2(rc, x, y);

	return res;
}


int main() {
	int n, m;

	std::cin >> n >> m;

	for (int i = 1; i <= n; i ++) {
		std::cin >> w[i];
	}

	build(1, 1, n);

	while (m --) {
		std::string op;
		int l, r;
		i64 d;

		std::cin >> op >> l >> r;

		if (op == "C") {
			std::cin >> d;
			update(1, l, l, d);
			if (r + 1 <= n) update(1, r + 1, r + 1, -d);
		} else {
			std::cout << std::abs(gcd(query2(1, 1, l), query(1, l + 1, r))) << "\n";
		}
	}
}

区间求max

​ 不需要讲啥的\(max\)的性质很多, 交换律,结合律都满足,直接求就行, 这里多提一嘴,区间更改直接记录add, 另外需要返回下标用\(pair\)结构来query.

#include <iostream>
#define lc p<<1
#define rc p<<1|1

using i64 = long long;

const int N = 2e5 + 10;

struct node {
	i64 l, r, sum, tag;
} tr[4 * N];

int w[N];

void pushup(int p) {
	tr[p].sum = std::max(tr[lc].sum, tr[rc].sum);

	if (tr[lc].sum > tr[rc].sum) tr[p].tag = tr[lc].tag;
	else tr[p].tag = tr[rc].tag;
}


void build(int p, int l, int r) {
	tr[p] = {l, r, w[l], l};
	if (l == r) return;
	int m = l + r >> 1;
	build(lc, l, m);
	build(rc, m + 1, r);
	pushup(p);
}

std::pair<i64, i64> query(int p, int x, int y) {
	if (tr[p].l >= x && tr[p].r <= y) {
		return {tr[p].sum, tr[p].tag};
	}
	
	int m = tr[p].l + tr[p].r >> 1;
	
	std::pair<i64, i64> res;

	res.first = -0x3f3f3f3f;

	if (x <= m) res = std::max(res, query(lc, x, y));
	if (y > m) res = std::max(res, query(rc, x, y));

	return res;
}

void update(int p, int x, int k) {
	if (tr[p].l == tr[p].r && tr[p].l == x) {	
		tr[p].sum = k;
		return;
	}

	int m = tr[p].l + tr[p].r >> 1;

	if (x <= m) update(lc, x, k);
	if (x > m) update(rc, x, k);

	pushup(p);
}

int main() {
	int n, q;

	std::cin >> n >> q;

	for (int i = 1; i <= n; i ++) std::cin >> w[i];	

	build(1, 1, n);
	
	while (q --) {
		int a, b, c, d;
		
		std::cin >> a >> b >> c;

		if (a) {
			auto t = query(1, b, c);
			std::cout << t.second << " " << t.first << "\n";
		} else {
			update(1, b, c);
		}
	}
}

扫描线

​ 具体做法就是从\(x\)轴遍历,存入\(y\)坐标当成矩形贡献,每次计算\(S \times 贡献数量\), 碰到矩形右边的时候,减去该贡献,由于坐标轴可能很大,我们可以将每条线离散化处理。

​ 模板:

Luogu-扫描线

#include <iostream>
#include <algorithm>
#define lc p<<1
#define rc p<<1|1
using i64 = long long;

const int N = 2e5 + 10;

struct Line {
	int x, y1, y2;
	int flag;
	bool operator<(Line &b) {return x < b.x;}
} L[N];

struct node {
	int l, r, len;
	int cnt;
} tr[8 * N];

int n, Y[N];

void pushup(int p) {
	if (tr[p].cnt) tr[p].len = tr[p].r - tr[p].l;
	else tr[p].len = tr[lc].len + tr[rc].len;
}

void build(int p, int l, int r) {
	tr[p].l = Y[l], tr[p].r = Y[r];
	if (r == l + 1) return;
	int mid = l + r >> 1;
	build(lc, l, mid);
	build(rc, mid, r);
}

void change(int p, int a, int b, int c) {
	if (a >= tr[p].r || b <= tr[p].l) return;
	if (a <= tr[p].l && tr[p].r <= b) {
		tr[p].cnt += c;
		pushup(p);
		return;
	}
	change(lc, a, b, c);
	change(rc, a, b, c);
	pushup(p);
}

int main() {
	int n;
	std::cin >> n;

	for (int i = 1; i <= n; i ++) {
		int x1, y1, x2, y2;
		std::cin >> x1 >> y1 >> x2 >> y2;
		L[i] = {x1, y1, y2, 1}, L[i + n] = {x2, y1, y2, -1};
		Y[i] = y1, Y[i + n] = y2;
	}

	n *= 2;	

	std::sort(L + 1, L + n + 1);
	std::sort(Y + 1, Y + n + 1);

	build(1, 1, n);
	
	i64 ans = 0;

	for (int i = 1; i < n; i ++) {
		change(1, L[i].y1, L[i].y2, L[i].flag);
		ans += 1LL * (L[i + 1].x - L[i].x) * tr[1].len;
	}

	std::cout << ans;
}

求历史最大值

求区间异或

​ 我们知道异或满足前缀和的性质:\(f(l, r) = f(r) - f(l - 1)\), 所基本上可以像求\(sum\)那样写。对于区间异或懒标记写法是独特的,因为我们对某个区间异或上固定的数多次,我们只取奇数次操作。

luogu 线段树xor

#include <iostream>
#define lc p<<1
#define rc p<<1|1
using i64 = long long;

const int N = 2e5 + 10;

int n, m;

struct node {
	int l, r, sum, add;
} tr[4 * N];

int w[N];

void pushup(int p) {
	tr[p].sum = tr[lc].sum + tr[rc].sum;
}

void pushdown(int p) {
	if (tr[p].add) {
		tr[lc].sum = (tr[lc].r - tr[lc].l + 1) - tr[lc].sum;
		tr[rc].sum = (tr[rc].r - tr[rc].l + 1) - tr[rc].sum;
		tr[lc].add ^= 1;
		tr[rc].add ^= 1;
		tr[p].add = 0;
	}
}

void build(int p, int l, int r) {
	tr[p] = {l, r, w[l], 0};
	if (l == r) return;
	int m = l + r >> 1;
	build(lc, l, m);
	build(rc, m + 1, r);
	pushup(p);
}

void update(int p, int x, int y) {
	if (tr[p].l >= x && tr[p].r <= y) {
		tr[p].sum = (tr[p].r - tr[p].l + 1) - tr[p].sum; 
		tr[p].add ^= 1;
		return;
	}	

	int m = tr[p].l + tr[p].r >> 1;

	pushdown(p);

	if (x <= m) update(lc, x, y);
	if (y > m) update(rc, x, y);

	pushup(p);
}

int query(int p, int x, int y) {
	if (tr[p].l >= x && tr[p].r <= y) {
		return tr[p].sum;
	}

	int m = tr[p].l + tr[p].r >> 1;

	pushdown(p);

	int cnt = 0;

	if (x <= m) cnt += query(lc, x, y);
	if (y > m) cnt += query(rc, x, y);

	return cnt;
}

int main() {
	std::cin >> n >> m; 

	for (int i = 1; i <= n; i ++) {
		char c;
		std::cin >> c;
		w[i] = c - '0';
	}

	build(1, 1, n);

	while (m --) {
		int op, l, r;

		std::cin >> op >> l >> r;

		if (!op) {
			update(1, l, r);
		} else {
			std::cout << query(1, l, r) << "\n";
		}
	}
}
posted on 2023-04-01 10:54  Jack404  阅读(15)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3