Segment tree beats(吉老师线段树)学习笔记

本文介绍了区间最值操作的概念,并通过吉老师线段树讲解了如何处理区间取min或max的问题。文章以HDU5306 Gorgeous Sequence和CCPC2020网络赛A题为例,详细分析了操作过程和复杂度,提供了相应的代码实现,总结了处理这类问题的关键点。

区间最值操作

区间最值操作,指的是对区间 [l,r][l,r][l,r] 里的全部数对 xxxmin⁡\minmin 或取 max⁡\maxmax,即 ai=min⁡(ai,x)a_i=\min(a_i,x)ai=min(ai,x)ai=max⁡(ai,x)a_i=\max(a_i,x)ai=max(ai,x)

一道例题

HDU5306 Gorgeous Sequence

在这里插入图片描述
我们用线段树维护每个区间的最大值 mxmxmx 和严格次大值 sesese,以及 mxmxmx 的个数 cntcntcnt
考虑 111 操作,对于一个线段树上的区间,我们分类讨论一下:

  • 如果 mx≤t:mx\leq t:mxt:
    那么我们可以直接 returnreturnreturn
  • 如果 mx>t,se<t:mx>t,se < t:mx>t,se<t:
    我们在区间打一个 tagtagtag 标记,并更新最大值为 ttt,同时更新 sum=sum−cnt∗(mx−t)sum = sum - cnt * (mx - t)sum=sumcnt(mxt)
  • 如果 se≥t:se\ge t:set:
    因为此时我们并不知道哪些数大于 ttt,于是我们暴力递归子区间

这样子操作的复杂度看起来很玄学,但其实通过势能分析,可以推出复杂度大概是 O(mlogn)O(mlogn)O(mlogn) 的,详情可参考吉老师 201620162016 年的国家集训队论文 (我实在不会啊QAQ

代码如下:

#include <bits/stdc++.h>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
LL z = 1;
int read(){
	int x, f = 1;
	char ch;
	while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
	x = ch - '0';
	while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48;
	return x * f;
}

const int N = 1e6 + 5;
int v1[N * 4], cnt[N * 4], tag[N * 4], v2[N * 4]; // v1是最大值,v2是次大值,tag 是最值标记,cnt 是最小值的个数 
LL sum[N * 4];

void pushup(int rt){
	if(v1[rt << 1] > v1[rt << 1 | 1]){
		v1[rt] = v1[rt << 1];
		cnt[rt] = cnt[rt << 1];
		v2[rt] = max(v2[rt << 1], v1[rt << 1 | 1]);
	}
	else if(v1[rt << 1] < v1[rt << 1 | 1]){
		v1[rt] = v1[rt << 1 | 1];
		cnt[rt] = cnt[rt << 1 | 1];
		v2[rt] = max(v1[rt << 1], v2[rt << 1 | 1]);
	}
	else{
		v1[rt] = v1[rt << 1];
		cnt[rt] = cnt[rt << 1] + cnt[rt << 1 | 1];
		v2[rt] = max(v2[rt << 1], v2[rt << 1 | 1]);
	}
	sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

void build(int l, int r, int rt){
	tag[rt] = -1;
	if(l == r){
		int x = read();
		v1[rt] = sum[rt] = x, v2[rt] = -1, cnt[rt] = 1;//次大值初始化为 -1 
		return;
	}
	int m = l + r >> 1;
	build(lson);
	build(rson);
	pushup(rt);
}

void addtag(int rt, int c){
	if(v1[rt] <= c) return;
	sum[rt] -= z * cnt[rt] * (v1[rt] - c);//更新 sum 
	v1[rt] = tag[rt] = c;//更新最值 
}

void pushdown(int rt){
	if(tag[rt] != -1){
		addtag(rt << 1, tag[rt]);//给左儿子加 tag 
		addtag(rt << 1 | 1, tag[rt]);//给右儿子加 tag 
		tag[rt] = -1;
	}
}

void update(int l, int r, int rt, int a, int b, int c){
	if(v1[rt] <= c) return;
	if(l >= a && r <= b && v2[rt] < c){//该区间的最小值都变成 c 
		addtag(rt, c);
		return;
	}
	pushdown(rt);
	int m = l + r >> 1;
	if(a <= m) update(lson, a, b, c);
	if(b > m) update(rson, a, b, c);
	pushup(rt);
}

int query_max(int l, int r, int rt, int a, int b){
	if(l >= a && r <= b) return v1[rt];
	int m = l + r >> 1, ans = 0;
	pushdown(rt);
	if(a <= m) ans = max(ans, query_max(lson, a, b));
	if(b > m) ans = max(ans, query_max(rson, a, b));
	return ans;
}

LL query_sum(int l, int r, int rt, int a, int b){
	if(l >= a && r <= b) return sum[rt];
	int m = l + r >> 1;
	LL ans = 0;
	pushdown(rt);
	if(a <= m) ans += query_sum(lson, a, b);
	if(b > m) ans += query_sum(rson, a, b);
	return ans;
}

int main(){
	int i, j, n, m, T, o, a, b, c;
	
	T = read();
	while(T--){
		n = read(); m = read();
		build(1, n, 1);
		for(i = 1; i <= m; i++){
			o = read(), a = read(), b = read();
			if(o == 0){
				c = read();
				update(1, n, 1, a, b, c);
			}
			else if(o == 1){
				printf("%d\n", query_max(1, n, 1, a, b));
			}
			else{
				printf("%lld\n", query_sum(1, n, 1, a, b));
			}
		}
	}
	return 0;
}



CCPC2020网络赛A art class

题意

在笛卡尔坐标系下,有 nnn 次操作,每次给出 (l,r,h)(l,r,h)(l,r,h),贴着 xxx 轴在 [l,r][l,r][l,r] 上放一个高为 hhh 的矩形,问每次操作后图形的总周长。
其中,n≤2e5,l,r,h≤1e9n\leq 2e5,l,r,h\leq 1e9n2e5,l,r,h1e9

分析

对于水平方向,就是一个简单的区间覆盖问题。
对于竖直方向,每次操作可以看作 [l,r][l,r][l,r]aia_iaihhhmax⁡\maxmax 。我们要求的,其实是 ∑∣ai−ai+1∣\sum |{a_i-a_{i+1}}|aiai+1
我们考虑用吉老师线段树维护这个东西,我们对区间 [l,r][l,r][l,r],要保存最小值 mnmnmn,次小值 sesese,以及 [l,r−1][l,r-1][l,r1]ai,ai+1a_i,a_{i+1}ai,ai+1 中有且仅有一个最小值的个数 cntcntcnt
这样,每次我们加 max⁡\maxmax 标记时,sum=sum−cnt∗(x−mn)sum=sum-cnt*(x-mn)sum=sumcnt(xmn)
总复杂度是 O(nlogn)O(nlogn)O(nlogn)

代码如下

#include <bits/stdc++.h>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define lc rt << 1
#define rc rt << 1 | 1
using namespace __gnu_pbds;
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;

LL z = 1;

int ksm(int a, int b, int p){
    int s = 1;
    while(b){
        if(b & 1) s = z * s * a % p;
        a = z * a * a % p;
        b >>= 1;
    }
    return s;
}

const int N = 4e5 + 5, inf = 1e9 + 5;
struct node{
    int l, r, h;
}q[N];
int v[N], tot, tag[N * 4], cnt[N * 4], mn[N * 4], tg[N * 4], se[N * 4], L[N * 4], R[N * 4];

LL v1[N * 4], v2[N * 4];

void pushup(int rt){
    v2[rt] = v2[lc] + v2[rc] + abs(L[rc] - R[lc]);
    L[rt] = L[lc];//最左端 
    R[rt] = R[rc];//最右端 
    if(mn[lc] < mn[rc]){
        cnt[rt] = cnt[lc];
        mn[rt] = mn[lc];
        se[rt] = min(se[lc], mn[rc]);
    }
    else if(mn[lc] > mn[rc]){
        cnt[rt] = cnt[rc];
        mn[rt] = mn[rc];
        se[rt] = min(mn[lc], se[rc]);
    }
    else{
        cnt[rt] = cnt[lc] + cnt[rc];
        mn[rt] = mn[lc];
        se[rt] = min(se[lc], se[rc]);
    }//上面是固定套路 
    if((R[lc] == mn[rt] || L[rc] == mn[rt]) && R[lc] != L[rc]) cnt[rt]++; //看连接处 
}

void pushup(int rt, int l, int r){
    if(tag[rt]) v1[rt] = v[r + 1] - v[l];
    else v1[rt] = v1[lc] + v1[rc];
}

void build(int l, int r, int rt){
	v1[rt] = v2[rt] = L[rt] = R[rt] = tag[rt] = tg[rt] = mn[rt] = cnt[rt] = 0;
    se[rt] = inf;
    if(l == r) return;
    int m = l + r >> 1;
    build(lson);
    build(rson);
}

void update1(int l, int r, int rt, int a, int b){
    if(tag[rt]) return;
    if(l >= a && r <= b){
        tag[rt] = 1;
        v1[rt] = v[r + 1] - v[l];
        return;
    }
    int m = l + r >> 1;
    if(a <= m) update1(lson, a, b);
    if(b > m) update1(rson, a, b);
    pushup(rt, l, r);
}

void addtag(int rt, int c){
    if(mn[rt] >= c) return;
    v2[rt] -= z * cnt[rt] * (c - mn[rt]);
    if(L[rt] == mn[rt]) L[rt] = c; //记得更新最左端和最右端 
    if(R[rt] == mn[rt]) R[rt] = c;
    tg[rt] = mn[rt] = c;
}

void pushdown(int rt){
    if(tg[rt]){
        addtag(lc, tg[rt]);
        addtag(rc, tg[rt]);
        tg[rt] = 0;
    }
}

void update2(int l, int r, int rt, int a, int b, int c){
    if(mn[rt] >= c) return;
    if(l >= a && r <= b && se[rt] > c){//固定套路 
        addtag(rt, c);
        return;
    }
    pushdown(rt);
    int m = l + r >> 1;
    if(a <= m) update2(lson, a, b, c);
    if(b > m) update2(rson, a, b, c);
    pushup(rt);
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int i, j, n, m, T, l, r, h;
    
    cin >> T;
    while(T--){
        cin >> n;
        tot = 0;
        for(i = 1; i <= n; i++){
            cin >> l >> r >> h;
            q[i] = {l, r, h};
            v[++tot] = l, v[++tot] = r;
        }
        sort(v + 1, v + tot + 1);
        m = unique(v + 1, v + tot + 1) - v - 1;//离散化 
        build(0, m + 1, 1); // 这里是 [0,m + 1],是因为两端的长度也要算入 
        for(i = 1; i <= n; i++){
            l = lower_bound(v + 1, v + m + 1, q[i].l) - v;
            r = lower_bound(v + 1, v + m + 1, q[i].r) - v;
            update1(0, m + 1, 1, l, r - 1);
            update2(0, m + 1, 1, l, r - 1, q[i].h);
            cout << v1[1] * 2 + v2[1] << "\n";
        }
    }
    return 0;
}

总结

这种对区间取 max⁡\maxmax 的题,一般要保存最小值 mnmnmn,严格次小值 sesese,以及和最小值相关的次数 cntcntcnt 还有一个标记 tagtagtag。每次加标记时,修改最小值 mnmnmn,同时更新要维护的值。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值