首页 > 其他分享 >线段树

线段树

时间:2025-01-19 18:56:05浏览次数:1  
标签:return lc int 线段 modify mid rc

海亮 OJ 题单

维护差分信息

P4243 [JSOI2009] 等差数列

若要在序列上处理等差数列,可以考虑差分法。此时,我们不必将差分数组和数列中的元素一一对应(这会影响理解),而是将差分数组中的一个元素和原序列中对应的两个元素关联(我的理解盲区)。

这样,使用线段树时,对于(差分数组的下标)区间 \([l,r]\),我们可以记录 \([l,r]\),\([l,r-1]\),\([l+1,r]\),\([l+1,r-1]\) 的答案;合并时,通过考虑这些区间对应到原序列的哪些元素,对左右区间的重合部分统计额外贡献,不重合的情况直接相加。

对于较短的区间(\(len\le 2\)),如何处理边界条件?当 \(len=1\) 时,区间 \([l+1,r]\) 或 \([l,r-1]\) 的答案在差分数组上无意义,但却能对应到原数组上的 \(1\) 个元素;对于 \([l+1,r-1]\),它在两个数组上都没有意义,因此直接赋值为 \(0\) 或 \(\pm\infty\)。

\(1\) 条差分信息 -> 约束两个位置
\(n\) 条差分信息 -> 约束 \(n+1\) 个位置

|
V

\(0\) 条差分信息 -> 代表一个位置

代码
#include<iostream>
using namespace std;
const int N = 1E5 + 10;
const int INF = 1E7 + 10;

struct Node {
	int mans, lans, rans, ans, tag;
	int ld, rd, len;
	Node(int _ma = 0, int _la = 0, int _ra = 0, int _a = 0, int _ld = 0, int _rd = 0, int _tg = 0, int _len = 0) {
		mans = _ma;
		lans = _la;
		rans = _ra;
		ans = _a;
		ld = _ld;
		rd = _rd;
		tag = _tg;
		len = _len;
	}
};

inline Node operator+(const Node &a, const Node &b) {
	Node res;
	res.mans = min(a.rans + b.lans - (a.rd == b.ld), min(a.mans + b.lans, a.rans + b.mans));
	res.lans = min(a.ans + b.lans - (a.rd == b.ld), min(a.lans + b.lans, a.ans + b.mans));
	res.rans = min(a.rans + b.ans - (a.rd == b.ld), min(a.mans + b.ans, a.rans + b.rans));
	res.ans = min(a.ans + b.ans - (a.rd == b.ld), min(a.lans + b.ans, a.ans + b.rans));
	res.ld = a.ld;
	res.rd = b.rd;
	return res;
}

int n, q; 
int a[N], d[N];

namespace Seg_T {
	
	inline int lc(int x) { return x << 1; }
	inline int rc(int x) { return x << 1 | 1; }
	Node tr[4 * N];
	inline void push_up(int p) {
		tr[p] = tr[lc(p)] + tr[rc(p)];
	}
	inline void move_tag(int p, int tg) {
		tr[p].ld += tg;
		tr[p].rd += tg;
		tr[p].tag += tg;
	}
	inline void push_down(int p) {
		if(!tr[p].tag) return;
		move_tag(lc(p), tr[p].tag);
		move_tag(rc(p), tr[p].tag);
		tr[p].tag = 0; 
	}
	void build(int p, int l, int r) {
		if(l == r) {
			tr[p] = {0, 1, 1, 1, d[l], d[l], 0};
			return;
		}
		int mid = (l + r) >> 1;
		build(lc(p), l, mid);
		build(rc(p), mid + 1, r);
		push_up(p); 
	} 
	void modify(int p, int l, int r, int q, int v) {
		if(l == r) {
			tr[p].ld += v;
			tr[p].rd += v;
			return;
		}
		push_down(p);
		int mid = (l + r) >> 1;
		if(mid >= q) modify(lc(p), l, mid, q, v);
		else modify(rc(p), mid + 1, r, q, v);
		push_up(p);
	}
	void modify(int p, int l, int r, int ql, int qr, int v) {
		if(ql <= l && r <= qr) {
			move_tag(p, v);
			return; 
		}
		push_down(p);
		int mid = (l + r) >> 1;
		if(mid >= ql) modify(lc(p), l, mid, ql, qr, v);
		if(mid < qr) modify(rc(p), mid + 1, r, ql, qr, v);
		push_up(p);
	}
	Node query(int p, int l, int r, int ql, int qr) {
		if(ql <= l && r <= qr) {
			return tr[p];
		}
		push_down(p);
		int mid = (l + r) >> 1;
		if(mid >= qr) return query(lc(p), l, mid, ql, qr);
		if(mid < ql) return query(rc(p), mid + 1, r, ql, qr);
		return query(lc(p), l, mid, ql, qr) + query(rc(p), mid + 1, r, ql, qr); 
	}
	
}

int main() {
	
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		d[i] = a[i] - a[i - 1];
	}
	
	Seg_T::build(1, 1, n + 1);
	
	cin >> q;
	while(q--) {
		char op;
		cin >> op;
		if(op == 'A') {
			int l, r, a, b;
			cin >> l >> r >> a >> b;
			if(l == r) {
				Seg_T::modify(1, 1, n + 1, l, a);
				Seg_T::modify(1, 1, n + 1, r + 1, -a); 
				continue;
			}
			Seg_T::modify(1, 1, n + 1, l, a);
			Seg_T::modify(1, 1, n + 1, l + 1, r, b);
			Seg_T::modify(1, 1, n + 1, r + 1, -a - (r - l) * b); 
		} else {
			int l, r;
			cin >> l >> r;
			if(l == r) {
				cout << 1 << '\n';
				continue;
			} 
			cout << Seg_T::query(1, 1, n + 1, l + 1, r).ans << '\n'; 
		}
	}
	
	return 0;
}

主席树

主席树是可持久化权值线段树的简称,可以在 \(O(\log V)\) 的时间和空间复杂度内实现一次插入,同时可以保存所有历史版本。

P3919 【模板】可持久化线段树 1(可持久化数组)

模板代码
#include<iostream>
using namespace std;
const int N = 1E6 + 10;
const int LOGN = 25; 

int n, q;
int a[N], rt[N];

namespace Seg_T {
	
	int nn;
	int lc[N * LOGN], rc[N * LOGN], sum[N * LOGN];
	
	int addNode(int p) {
		int nw = ++nn;
		lc[nw] = lc[p];
		rc[nw] = rc[p];
		return nw;
	}
	
	inline void push_up(int p) {
		sum[p] = sum[lc[p]] + sum[rc[p]];
	}
	
	void build(int &p, int l, int r) {
		if(p == 0) p = ++nn;
		if(l == r) {
			sum[p] = a[l];
			return;
		}
		int mid = (l + r) >> 1;
		build(lc[p], l, mid);
		build(rc[p], mid + 1, r);
		push_up(p); 
	}
	
	int modify(int p1, int l, int r, int q, int v) {
		int p = addNode(p1);
		if(l == r) {
			sum[p] = v;
			return p;
		}
		int mid = (l + r) >> 1;
		if(mid >= q) lc[p] = modify(lc[p1], l, mid, q, v);
		else rc[p] = modify(rc[p1], mid + 1, r, q, v);
		push_up(p);
		return p;
	}
	
	int query(int p, int l, int r, int q) {
		if(l == r) {
			return sum[p];
		}
		int mid = (l + r) >> 1;
		if(mid >= q) return query(lc[p], l, mid, q);
		else return query(rc[p], mid + 1, r, q); 
	}
	
}

int main() {
	
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n >> q;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
	} 
	
	Seg_T::build(rt[0], 1, n);
	
	for(int i = 1; i <= q; i++) {
		int ver, op, pos, val;
		cin >> ver >> op;
		if(op == 1) {
			cin >> pos >> val;
			rt[i] = Seg_T::modify(rt[ver], 1, n, pos, val);
		} else {
			cin >> pos;
			cout << Seg_T::query(rt[ver], 1, n, pos) << '\n';
			rt[i] = rt[ver]; 
		}
	}
	
	return 0;
}

使用主席树我们可以对原数组的每一个前缀都“建”一棵权值线段树,通过对两棵不同下标对应的树作差,我们可以对任意下标区间 \([l,r]\) 使用线段树二分,解决静态区间第 \(k\) 小的问题。

P3834 【模板】可持久化线段树 2

模板代码
#include<iostream>
#define int long long
using namespace std;
const int N = 2E5 + 10;
const int A = 1E9; 
const int LOGA = 20;

int n, m; 
int rt[N];

namespace Seg_T {
	
	int nn;
	int lc[N * LOGA], rc[N * LOGA], sum[N * LOGA];
	
	int addNode(int p1) {
		int p = ++nn;
		lc[p] = lc[p1];
		rc[p] = rc[p1];
		sum[p] = sum[p1];
		return p;
	}
	
	void push_up(int p) {
		sum[p] = sum[lc[p]] + sum[rc[p]];
	}
	
	int insert(int p1, int l, int r, int q, int v) {
		int p = addNode(p1);
		if(l == r) {
			sum[p] += v;
			return p;
		}
		int mid = (l + r) >> 1;
		if(mid >= q) lc[p] = insert(lc[p1], l, mid, q, v);
		else rc[p] = insert(rc[p1], mid + 1, r, q, v);
		push_up(p); 
		return p;
	}
	
	int queryKth(int pl, int pr, int l, int r, int k) {
		if(l == r) {
			return l;
		}
		int mid = (l + r) >> 1;
		if(k <= sum[lc[pr]] - sum[lc[pl]]) return queryKth(lc[pl], lc[pr], l, mid, k);
		else return queryKth(rc[pl], rc[pr], mid + 1, r, k - (sum[lc[pr]] - sum[lc[pl]]));
	}
	
}

signed main() {
	
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n >> m;
	for(int i = 1; i <= n; i++) {
		int x;
		cin >> x;
		rt[i] = Seg_T::insert(rt[i - 1], 0, A, x, 1);
	}
	
	for(int i = 1; i <= m; i++) {
		int l, r, k;
		cin >> l >> r >> k;
		cout << Seg_T::queryKth(rt[l - 1], rt[r], 0, A, k) << '\n';
	}
	
	return 0;
}

时间复杂度 \(O(n\log V)\) - \(O(m\log V)\)。

线段树优化建图

标签:return,lc,int,线段,modify,mid,rc
From: https://www.cnblogs.com/SimonHTC/p/18679774

相关文章

  • 【做题记录】2025刷题计划--线段树
    A.「SDOI2014」旅行给每个宗教开一棵线段树,树剖\(+\)线段树单点修改区间查询即可。Code#include<bits/stdc++.h>#definelllonglong#defineilinline#defineread(x){\ charch;\ intfu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&1......
  • 计算几何~三角形面积、点在三角形内、线段相交代码笔记
    多边形面积的基本公式:鞋带公式。强调多边形点集是按顺序存储;三角形面积基本公式:海伦公式;向量叉积公式;拓扑关系判断:判断点是否在三角形内;判断两条线段是否相交;代码笔记:#pragmaonce#include<iostream>#include<vector>#include<algorithm>#include<cmath>#in......
  • 势能线段树
    简介通过定义势能函数\(\phi(i)\)去描绘整个序列的势能从而推导正确的复杂度。例题P4145上帝造题的七分钟2/花神游历各国典。设\(\phi(i)\)表示第\(i\)个元素的势能。一个元素不停的开根一定会变成\(1\),不妨将元素\(x\)改写成\(2^k\)的形式。一次开根会将\(......
  • 线段树合并
    简介将多棵线段树的信息统一起来的高效算法称之为线段树合并。通常合并顺序呈树状结构。例题P3224[HNOI2012]永无乡假设所有点都在一个连通块里,那么我们只需要维护一个值域线段树并在上面二分即可。但此时图不连通,我们该如何快速的统计信息呢?对于连边,并查集可以胜任。对......
  • 洛谷题单指南-线段树的进阶用法-P3168 [CQOI2015] 任务查询系统
    原题链接:https://www.luogu.com.cn/problem/P3168题意解读:一个任务管理系统,能够查询在某个时间点运行的任务中优先级最小的k个任务的优先级之和。解题思路:由于总时间n不超过100000,考虑针对所有时刻建立可持久化线段树,根节点为root[i]的线段树维护时刻i的任务情况,节点区间表示......
  • 洛谷题单指南-线段树的进阶用法-P2617 Dynamic Rankings
    原题链接:https://www.luogu.com.cn/problem/P2617题意解读:动态求区间第k小问题。解题思路:树套树的典型应用,具体阐述参考:https://www.cnblogs.com/jcwy/p/18640914100分代码:#include<bits/stdc++.h>usingnamespacestd;constintN=100005;structOp{charop;......
  • 解题报告-论对“线段树思想”的新理解
    解题报告-论对“线段树思想”的新理解一晚上刷了两个线段树知识点,也是见识到了线段树世界的博大精深。我们发现无论怎么写线段树,大体框架都是一样的。那么为什么有那么多种线段树呢?一个是线段树标记的不同。在李超线段树中,每个结点维护的是当前结点最上面那条线的编号,于是更新......
  • 线段树学习笔记
    什么是线段树线段树是一种基于分治思想的二叉树结构,用于在区间上进行信息统计,比树状数组更为通用、直观,支持单点修改、区间修改、区间查询。线段树维护的数据具有可并性,比如区间和、区间积、区间最值等等。模板建树voidbuild(intl,intr,intp){ tre[p].l=l;tre[p].r=r;......
  • 线段树【区间GCD】
    https://codeforces.com/contest/2050/problem/F#include<bits/stdc++.h>#definelcp<<1#definercp<<1|1#defineINF2e9usingnamespacestd;#definelowbit(x)x&(-x)#defineendl'\n'usingll=longlong;usingpii=pair......
  • 64.在Vue3中使用OpenLayers显示带箭头的线段,箭头居中
    在WebGIS开发中,使用OpenLayers渲染地图和矢量图形是常见的需求。今天我们来实现一个效果:在Vue3项目中,使用OpenLayers显示带箭头的线段,并让箭头居中。项目环境和技术栈Vue3+CompositionAPIOpenLayersVite构建工具实现效果我们将绘制一条由多个坐标点构成的线......