洛谷 P6242 【模板】线段树 3 吉司机线段树

传送门

大佬题解传送门

这篇博客很详细,但是大佬没在代码里面标明每一步的含义,我稍微写下注释。

还是想证明一下为什么要维护四个变量0.0(主要是证明为什么不写四个会出错)

不得不先证明为什么要维护加法懒标记出现的最大值。我们先不管那个最大和非最大。

  假设我们的根节点u维护的区间是[1, 6],左儿子ls维护的区间是[1, 3],右儿子rs维护的区间是[4, 6]。假设区间所有数字初始为0。

  定义add1加法懒标记, add2为add1未下传之前的最大值。mx是区间最大值。hismx是区间历史最大值。mx2为区间严格次大值。

  ① 输入1 1 6 2。此时tr[u].add1 = tr[u].add2 = tr[u].mx = tr[u].hismx = 2;

  ② 输入1 1 6 -3。此时tr[u].add1 = -1,tr[u].add2 = tr[u].hismx = 2,tr[u].mx = -1;

  ③ 输入1 1 3 -3。此时我们会发现如果我们下传的标记只有add1的话是不对的,因为能让ls的hismx最大的时候一定是父节点未下传时刻的过程中的最大懒标记。如果我们只下传add1会导致tr[ls].mx = -1,但是实际上最优的是在操作1的时候下传,tr[ls].mx就会等于2。但是由于我们要利用懒标记的延时性来减少时间复杂度,我们不能做到及时下传,所以我们维护一个区间加法懒标记的最大值即可。

再证明一下为什么要维护非最大值的加法懒标记以及非最大值懒标记的最大值

  定义add1为维护最大值加法的懒标记,add2为维护最大值加法的懒标记的最大值,add3为维护非最大值加法的懒标记,add4为非最大值加法的懒标记的最大值。cnt为最大值出现的次数,sum为区间和,hismx为区间历史最大值,mx为区间最大值,mx2为区间严格次大值。

  为什么需要维护最大值和非最大值加法懒标记和最大加法懒标记?难道不是任何时候都应该加相同的数字吗?为什么最假女拳手不需要维护这些信息?

  我们思考一个问题,如果不维护关于非最大值的两个加法懒标记。父节点u维护信息的时候,如果最大值只是来源于左儿子ls,右儿子并不提供最大值,在u取完min再进行加法的时候是不是会出现一些问题?,因为取min只是针对于区间最大值的,而非最大值不需要取min,那么对于结点u而言,是不是非区间最大值进行的加法懒标记要比非区间最大值进行的加法懒标记更大?

  举个栗子:假设我们的根节点u维护的区间是[1, 6],左儿子ls维护的区间是[1, 3],右儿子rs维护的区间是[4, 6]。假设区间所有数字初始为[1, 2, 3, 4, 5, 6]。区间和自己看代码就能明白。

  ①1 1 6 5。此时tr[u].add1 = tr[u].add2 = tr[u].add3 = tr[u].add4 = 5, tr[u].mx = tr[u].hismx = 11;

  ②2 1 6 10。因为我们会递归到mx2 < 10,很明显非最大值不会取min此时只会有最大值受影响。tr[u].add1 = 4, add2 = tr[u].add3 = tr[u].add4 = 5。tr[u].mx = 10, tr[u].hismx = 11;

  ③1 1 6 2。此时tr[u].add1 = 6, tr[u].add2 = 6, tr[u].add3 = tr[u].add4 = 7, tr[u].mx = 12, tr[u].hismx = 12。这个时候我们就会发现实际上区间非最大值(add4)要加上的最大值实际上比父节点u最大值加法未下传之前的最大值(add2)要大(tr[u].add2 < tr[u].add4),那么对于ls而言最大值(add1,add2)和非最大值(add3,add4)都是需要用tr[u].add3和tr[u].add4维护(因为ls不给u提供最大值,所以tr[u].add3和tr[u].add4作用于ls的add1,add2,add3,add4),这么一来对于左子树而言,父节点的非最大值加法懒标记的最大值(add3,add4)才是左子树需要的正确信息,如果我们不维护add3和add4,用add1和add2的信息给ls就会导致ls的历史最大值偏小。

  剩下的自己稍微看看是能理解的。


#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <stack>
#include <queue>
#include <numeric>
#include <cassert>
#include <bitset>
#include <cstdio>
#include <vector>
#include <unordered_set>
#include <cmath>
#include <map>
#include <unordered_map>
#include <set>
#include <deque>
#include <tuple>
 
#define all(a) a.begin(), a.end()
#define cnt0(x) __builtin_ctz(x)
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define rep(i, a, b) for(int i = a;i <= b; i ++)
#define per(i, a, b) for(int i = a;i >= b; i --)
#define cntone(x) __builtin_popcount(x)
#define db double
#define fs first
#define se second
#define AC main(void)
#define ls(u) u << 1
#define rs(u) u << 1 | 1
#define HYS std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
const long double eps = 1e-9;
const int N = 5e5 + 10;
const int INF = 0x3f3f3f3f;
int MOD = 571373;
const int LO = 1 << 20 | 1;
char buffer[LO],*S,*TT;
#define getchar() ((S==TT&&(TT=(S=buffer)+fread(buffer,1,LO,stdin),S==TT))?EOF:*S++)

namespace Fio {
    inline std::string sread() {
        std::string s = " ";
        char e = getchar();
        while (!isdigit(e) && !isalpha(e) && e != '*') e = getchar();
        while (isdigit(e) || isalpha(e) || e == '*') s += e, e = getchar();
        return s;
    }
    
    inline int read() {
        int x = 0, y = 1;
        char c = getchar();
        while (!isdigit(c)) {
            if (c == '-') y = -1;
            c = getchar();
        }
        while (isdigit(c)) {
            x = (x << 3) + (x << 1) + (c ^ 48);
            c = getchar();
        }
        return x *= y;
    }
    
    inline ll readll() {
        ll x = 0, y = 1;
        char c = getchar();
        while (!isdigit(c)) {
            if (c == '-') y = -1;
            c = getchar();
        }
        while (isdigit(c)) {
            x = (x << 3) + (x << 1) + (c ^ 48);
            c = getchar();
        }
        return x *= y;
    }
    inline void write(ll x) {
        if (x < 0) x = -x, putchar('-');
        ll sta[35], top = 0;
        do sta[top++] = x % 10, x /= 10;
        while (x);
        while (top) putchar(sta[--top] + '0');
        putchar('\n');
    }
    
    inline void write(int x) {
        if (x < 0) x = -x, putchar('-');
        int sta[35], top = 0;
        do sta[top++] = x % 10, x /= 10;
        while (x);
        while (top) putchar(sta[--top] + '0');
        putchar('\n');
    }
} using namespace Fio;

int n , m, _;
int ans = INF;

int d1[] = {0, 0, 1, -1};
int d2[] = {1, -1, 0, 0};

int a[N];

//add1最大值加减标记  add2最大值历史最大的加减标记
//add3非最大值加减标记  add4非最大值历史最大的加减标记
//mx区间最大值 mx2严格次大值 cnt区间最大值出现的次数 hismx历史区间最大值 sum区间和
struct Segment_tree{
	int l, r;
	int mx, mx2, cnt, hismx;
	int add1, add2, add3, add4;
	ll sum;
}tr[N << 2];

int max(int a, int b){return a > b ? a : b;}
int min(int a, int b){return a < b ? a : b;}

inline void pushup(int u){
	tr[u].hismx = max(tr[ls(u)].hismx, tr[rs(u)].hismx);
	tr[u].sum = tr[ls(u)].sum + tr[rs(u)].sum;
	if(tr[ls(u)].mx == tr[rs(u)].mx){
		tr[u].mx = tr[ls(u)].mx;
		tr[u].cnt = tr[ls(u)].cnt + tr[rs(u)].cnt;
		tr[u].mx2 = max(tr[ls(u)].mx2, tr[rs(u)].mx2);
	}else if(tr[ls(u)].mx > tr[rs(u)].mx){
		tr[u].mx = tr[ls(u)].mx;
		tr[u].cnt = tr[ls(u)].cnt;
		tr[u].mx2 = max(tr[ls(u)].mx2, tr[rs(u)].mx);
	}else{
		tr[u].mx = tr[rs(u)].mx;
		tr[u].cnt = tr[rs(u)].cnt;
		tr[u].mx2 = max(tr[rs(u)].mx2, tr[ls(u)].mx);
	}
}

inline void build(int u, int l, int r){
	tr[u] = {l, r};
	if(l == r){
		tr[u].mx = tr[u].sum = tr[u].hismx = read();
		tr[u].mx2 = -INF; tr[u].cnt = 1;
		return ;
	}
	
	int mid = l + r >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
	
	pushup(u);
}

//k1是区间最大值的加法懒标记 k2是区间最大值加法懒标记的最大值
//k3是区间非最大值的加法懒标记 k4是区间非最大值加法懒标记的最大值
inline void update(int u, int k1, int k2, int k3, int k4){
	//区间最大值加法当然只对最大值生效,所以用最大值的数量*k1来更新
	//区间非最大值加法同理
	tr[u].sum += 1ll * tr[u].cnt * k1 + 1ll * (tr[u].r - tr[u].l + 1 - tr[u].cnt) * k3;
	//区间历史最大值要和区间最大值加上下传下来的最大懒加法标记进行取max
	//ps:因为在父节点被修改成历史最大的时未必会及时下传,所以把最大的修改值记下来再下传
	tr[u].hismx = max(tr[u].hismx, tr[u].mx + k2);//更新区间历史最大值
	tr[u].mx += k1; 
	if(tr[u].mx2 != -INF)	tr[u].mx2 += k3;
	//没有来的及下传的最大值加法懒标记的最大值需要和区间最大值未及时下传的加法标记加上上一步传下来的最大值取max
	//ps:因为add2只是过程中的最大值,并不是实时值,所以需要用实时值和父亲传下来的最大值加起来取max
	tr[u].add2 = max(tr[u].add2, tr[u].add1 + k2);
	tr[u].add4 = max(tr[u].add4, tr[u].add3 + k4);
	tr[u].add1 += k1;
	tr[u].add3 += k3;
}

inline void pushdown(int u){
	//确定父亲的最大值来自于哪个子区间
	int mx = max(tr[ls(u)].mx, tr[rs(u)].mx);
	//如果左儿子的最大值等于左右儿子区间的最大值,则说明父亲的最大值是有通过左儿子更新,需要下传add1和add2。
	//否则不是通过左儿子,左儿子的最大值和非最大值全用add3和add4维护
	if(tr[ls(u)].mx == mx)	update(ls(u), tr[u].add1, tr[u].add2, tr[u].add3, tr[u].add4);
	else update(ls(u), tr[u].add3, tr[u].add4, tr[u].add3, tr[u].add4);
	//理由同上
	if(tr[rs(u)].mx == mx)	update(rs(u), tr[u].add1, tr[u].add2, tr[u].add3, tr[u].add4);
	else update(rs(u), tr[u].add3, tr[u].add4, tr[u].add3, tr[u].add4);

	tr[u].add1 = tr[u].add2 = tr[u].add3 = tr[u].add4 = 0;
}

inline void Add(int u, int l, int r, int v){
	if(tr[u].l >= l && tr[u].r <= r){
		update(u, v, v, v, v);
		return ;
	}	
	
	pushdown(u);
	
	int mid = tr[ls(u)].r;
	if(l <= mid)	Add(u << 1, l, r, v);
	if(r > mid)	Add(u << 1 | 1, l, r, v);

	pushup(u);
}

inline void getmin(int u, int l, int r, int v){
	if(tr[u].mx <= v)	return ;
        //这里的update填的参数与大佬略有不同,因为我认为如果加法懒标记出现的最大值如果小于0了是没有意义的,无法产生有效影响。
	if(tr[u].l >= l && tr[u].r <= r && tr[u].mx2 < v){
		update(u, v - tr[u].mx, 0, 0, 0);
		return ;
	}
		
	pushdown(u);
	
	int mid = tr[ls(u)].r;
	if(l <= mid)	getmin(u << 1, l, r, v);
	if(r > mid)	getmin(u << 1 | 1, l, r, v);	

	pushup(u);
}

inline int queryhismax(int u, int l, int r){
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].hismx;
	
	int res = -INF;
	int mid = tr[ls(u)].r;
	pushdown(u);	

	if(l <= mid)	res = max(res, queryhismax(u << 1, l, r));
	if(r > mid)	res = max(res, queryhismax(u << 1 | 1, l, r));
	
	return res;
}

inline int querymax(int u, int l, int r){
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].mx;
	
	int res = -INF;
	int mid = tr[ls(u)].r;
	pushdown(u);	

	if(l <= mid)	res = max(res, querymax(u << 1, l, r));
	if(r > mid)	res = max(res, querymax(u << 1 | 1, l, r));
	
	return res;
}

inline ll querysum(int u, int l, int r){
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].sum;
	
	ll res = 0;
	int mid = tr[ls(u)].r;
	pushdown(u);	

	if(l <= mid)	res += querysum(u << 1, l, r);
	if(r > mid)	res += querysum(u << 1 | 1, l, r);
	
	return res;
}

inline void solve(){
	n = read(), m = read();
	
	build(1, 1, n);

	while(m --){
		int op, l, r, x;
		op = read(), l = read(), r = read();
		if(op == 1){
			x = read();
			Add(1, l, r, x);
		}else if(op == 2){
			x = read();
			getmin(1, l, r, x);
		}else if(op == 3){
			write(querysum(1, l, r));
		}else if(op == 4){
			write(querymax(1, l, r));
		}else{
			write(queryhismax(1, l, r));
		}
	}
}


int main(){
   	
	_ = 1;
   	//_ = read();
	while(_ --)
    	solve();

    return 0;
}
posted @ 2023-03-25 10:48  春始于雪之下  阅读(14)  评论(0编辑  收藏  举报