在学校集训的时候老师准备把线段树从0基础讲到CSP-S的难度(正好水一篇)
顺便用博客记录一下吧
Part01:什么是线段树+基础的线段树怎么写
看,这就是线段树!讲完了!(bushi)
树状数组大家都学过吧,相信你肯定学过了
没学过的还不快去学!!!!
线段树(Segment Tree),适用于在区间上统计比如区间和,区间最大值等等的二叉树
这些性质应该很好理解吧,不理解的评论区见
1.1建立线段树
上面4行是定义一个线段树
这里的模板是求区间最大值,RMQ的意思是求区间的最值
我们用递归更新子节点,但是子节点计算完之后还是要更新父节点的
build函数中的l == r就代表的是叶子节点,可以直接赋值点的值
1.2线段树的单点修改
只需要从根节点搜下来,在随个回溯更新即可
x代表你要修改的下标,v是你要修改的值
假如他在此时的根节点的左边,那么根节点就变成了左子树的根节点反之亦然
同样不要忘记回溯时更新
1.3线段树的区间查询![](https://i-blog.csdnimg.cn/blog_migrate/ed116b680376fbe649faa5f1779943a4.png)
这里大家先看代码
若查询的区间和左子树有交集就计算左子树,反之亦然
这里注意一下,被查询区间要完全包含当前节点管辖区间才可以回溯他的权值
这三个函数都是时间复杂度logN的
code:
//线段树的建立
struct SegmentTree{
int l, r;//区间
int dat; //维护的值
} t[SIZE * 4]; //建立数组,4倍空间
void build(int p, int l, int r){ //建立线段树
t[p].l = l, t[p].r = r; //节点p代表区间[l,r]
if(l == r){ //叶节点
t[p].dat = a[l];
return;
}
int mid = (l+r)/2; //折半
build(p*2, l, mid); //左子节点
build(p*2+1, mid+1, r) //右子节点
//完成递归后,注意维护父节点的属性
t[p].dat = max(t[p*2].dat, t[p*2+1].dat); //由下往上维护RMQ性质
}
//main函数中
build(1,1,n);
//a={3,6,4,8,1,2,9,5,7,0}
//线段树的单点修改
void modify(int p, int x, int v){
if(t[p].l == t[p].r) { //叶节点
t[p].dat = v;
return;
}
int mid = (t[p].l + t[p].r)/2;
if(x <= mid) modify(p*2, x ,v) //x属于左子树区间
else modify(p*2+1, x, v) //x属于右子树区间
t[p].dat = max(t[p*2].dat, t[p*2+1].dat); //由下往上维护RMQ性质
}
modify(1,x,v);
//modify(1,7,1)
//线段树的区间查询
int query(int p, int l, int r){
//当前节点管辖的区间,被 需查询的区间[l,r] 完全覆盖
if(l <= t[p].l && r >= t[p].r) return t[p].dat;
int mid = (t[p].l + t[p].r)/2;
int val = -(1<<30); //负极大数
if(l <= mid) val = max(val, query(p*2, l, r)); //左子节点有覆盖
if(r > mid) val = max(val, query(p*2+1, l, r)); //右子节点有覆盖
return val;
}
cout << query(1,l,r) << endl; //调用入口
练习题:Acwing245区间最大连续子段和
传送门
245. 你能回答这些问题吗 - AcWing题库高质量的算法题库https://www.acwing.com/problem/content/246/
Part02:Lazy Tag懒标记
这个东西的来由是线段树的区间修改
区间修改可能会做到O(n)的复杂度
而且可能查询时还用不到,那么懒标记就出场了,它可以通过更新一个点来存储更新一个区间
举个我们老师举得例子
你是一个10户人家的楼的楼长,居委会会检查你们楼的物资分配情况
你就可以在居委会检查之前再把物资发放,并且保证不会饿死人
这样就可以省力了
code:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 500006, INF = 0x3f3f3f3f;
int n, m, a[N];
struct T {
int l, r, s, lx, rx, ans;
} t[N*4];
void build(int p, int l, int r) {
t[p].l = l;
t[p].r = r;
if (l == r) {
t[p].s = t[p].lx = t[p].rx = t[p].ans = a[l];
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 + 1, mid + 1, r);
t[p].s = t[p<<1].s + t[p<<1|1].s;
t[p].lx = max(t[p<<1].lx, t[p<<1].s + t[p<<1+1].lx);
t[p].rx = max(t[p<<1+1].rx, t[p<<1].rx + t[p<<1+1].s);
t[p].ans = max(max(t[p<<1].ans, t[p<<1+1].ans), t[p<<1].rx + t[p<<1+1].lx);
}
void change(int p, int x, int y) {
if (t[p].l == t[p].r) {
t[p].s = t[p].lx = t[p].rx = t[p].ans = y;
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) change(p << 1, x, y);
else change(p << 1 | 1, x, y);
t[p].s = t[p<<1].s + t[p<<1|1].s;
t[p].lx = max(t[p<<1].lx, t[p<<1].s + t[p<<1|1].lx);
t[p].rx = max(t[p<<1|1].rx, t[p<<1].rx + t[p<<1|1].s);
t[p].ans = max(max(t[p<<1].ans, t[p<<1|1].ans), t[p<<1].rx + t[p<<1|1].lx);
}
T ask(int p, int l, int r) {
if (l <= t[p].l && r >= t[p].r) return t[p];
T a, b, ret;
a.s = a.lx = a.rx = a.ans = b.s = b.lx = b.rx = b.ans = -INF;
ret.s = 0;
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) {
a = ask(p << 1, l, r);
ret.s += a.s;
}
if (r > mid) {
b = ask(p << 1 | 1, l, r);
ret.s += b.s;
}
//进行答案各个属性的维护
ret.ans = max(max(a.ans, b.ans), a.rx + b.lx);
ret.lx = max(a.lx, a.s + b.lx);
ret.rx = max(b.rx, b.s + a.rx);
if (l > mid) ret.lx = max(ret.lx, b.lx);
if (r <= mid) ret.rx = max(ret.rx, a.rx);
return ret;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
build(1, 1, n);
while (m--) {
int t, x, y;
scanf("%d %d %d", &t, &x, &y);
if (t == 1) {
if (x > y) swap(x, y);
cout << ask(1, x, y).ans << endl;
}
else change(1, x, y);
}
return 0;
}
练习题:洛谷2894hotelG
[USACO08FEB] Hotel G - 洛谷https://www.luogu.com.cn/problem/P2894
Part03:扫描线
例题:Atlanits Acwing247
code:
//1≤n≤10000
//0≤x1<x2≤100000
//0≤y1<y2≤100000
#include<bits/stdc++.h>
using namespace std;
#define ls(x) (x << 1) //左儿子
#define rs(x) ((x << 1) + 1) //右儿子
vector< pair<double, pair< pair< double, double>, int > > > dir; //x,y1,y2,1 线段边界四元组
set<double> st; //用于去重和排序
unordered_map<double,int> val; //离散化
double raw[20020];
int n;
struct SegNode{ //定义线段树的节点
int l,r,cnt;
double len;
#define l(x) t[x].l
#define r(x) t[x].r
#define cnt(x) t[x].cnt
#define len(x) t[x].len
}t[20020 * 8];
void build(int p, int l, int r){
l(p) = l; r(p) = r; cnt(p) = 0; len(p) = 0;
if(l == r) return;
int mid = (l + r) >> 1;
build(ls(p), l, mid);
build(rs(p), mid + 1, r);
}
void update(int p, int l, int r, int k){
if(l <= l(p) && r >= r(p)) {//更新区间大于管辖区间
cnt(p) += k; //覆盖次数+1
//如果p点管辖区间的覆盖次数至少大于等于1次,则len(p)为 区间长度(r+1-l)
//否则,就是左、右儿子覆盖区间之和(不一定全覆盖,递归下去找,可能中间有间隔)
len(p) = cnt(p) > 0 ? raw[r(p) + 1] - raw[l(p)] : len(ls(p)) + len(rs(p)) ;
return ;
}
//如果更新区间不全覆盖p点管辖区间。递归更新
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) update(ls(p), l, r, k);
if(r > mid) update(rs(p), l, r, k);
//回溯更新,维护len(p)
len(p) = cnt(p) > 0 ? raw[r(p) + 1] - raw[l(p)] : len(ls(p)) + len(rs(p)) ;
return ;
}
void ini(){
dir.clear();
val.clear();
//raw.clear();
st.clear();
//memset(t,0,sizeof t);
}
int main() {
int testcase = 0;
while(1) {
//cin >> n;
scanf("%d", &n);
if(n == 0) return 0;
ini();
//double a = 0, b = 0, c = 0, d = 0;
double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
for(int i = 1; i <= n; i++){
//cin >> a >> b >> c >> d;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
dir.push_back({x1,{{y1,y2}, 1}}); //左边界
dir.push_back({x2,{{y1,y2},-1}}); //右边界
st.insert(y1);st.insert(y2);
}
sort(dir.begin(),dir.end()); //四元组排序
//遍历set
set<double> ::iterator it; //key就是value
int i = 0;
for(it = st.begin(); it != st.end(); it++) {
val[*it] = ++i; //*it即为y的值
raw[i] = *it;
}
//在1~i-1区间内建立线段树
build(1,1,i-1);
double x = 0,ans = 0;
for(auto &P:dir) {
ans += (P.first - x) * len(1); //从根节点递归下去,更新覆盖区间长度之和
//update(root,y1,y2-1,1 or -1)
update(1,val[P.second.first.first],val[P.second.first.second] - 1,P.second.second);
x = P.first; //当前这次的横坐标,传递给下一次循环
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",++testcase,ans);
}
}
后面还有内容,不过今天就讲到这里吧
求一键三连+转发,这些是对我最大的帮助
我们下期再见qwq