暑期特刊01:线段树

在学校集训的时候老师准备把线段树从0基础讲到CSP-S的难度(正好水一篇

顺便用博客记录一下吧

Part01:什么是线段树+基础的线段树怎么写

      看,这就是线段树!讲完了!(bushi)  

树状数组大家都学过吧,相信你肯定学过了

        没学过的还不快去学!!!!

        线段树(Segment Tree),适用于在区间上统计比如区间和,区间最大值等等的二叉树

         这些性质应该很好理解吧,不理解的评论区见

       1.1建立线段树

        上面4行是定义一个线段树

        这里的模板是求区间最大值,RMQ的意思是求区间的最值

        我们用递归更新子节点,但是子节点计算完之后还是要更新父节点的

        build函数中的l == r就代表的是叶子节点,可以直接赋值点的值

       1.2线段树的单点修改

        只需要从根节点搜下来,在随个回溯更新即可 

        

        x代表你要修改的下标,v是你要修改的值

        假如他在此时的根节点的左边,那么根节点就变成了左子树的根节点反之亦然

        同样不要忘记回溯时更新

      1.3线段树的区间查询

        这里大家先看代码

        若查询的区间和左子树有交集就计算左子树,反之亦然

        这里注意一下,被查询区间要完全包含当前节点管辖区间才可以回溯他的权值

        这三个函数都是时间复杂度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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值