区间最值操作,区间历史最值

将区间内所有数同某数取\(min,max\),将这一类操作统称区间最值操作,接下来具体讲解其解决方法。
一道板子题
\(P6242\)
https://www.luogu.com.cn/problem/P10639
题意:给定一个序列,维护六种操作:
\(1\):给定\(x,y,z\),将\([x,y]\)内所有数加上\(z\)
\(2\):给定\(x,y,z\),将\([x,y]\)内所有数同\(z\)\(max\)
\(3\):给定\(x,y,z\),将\([x,y]\)内所有数同\(z\)\(min\)
\(4\):给定\(x,y\),求\([x,y]\)内所有数的和。
\(5\):给定\(x,y\),求\([x,y]\)内所有数的最大值。
\(6\):给定\(x,y\),求\([x,y]\)内所有数的最小值。
题解:\(2,3\)操作是将连续区间内若干分散的数赋值成同一数,由于要查询区间和,常规的懒标记无法解决。
考虑较为暴力的做法,先维护区间和\(s\),区间最小值\(mi_1\),区间严格次小值\(mi_2\),区间最大值\(mx_1\),区间严格最次大值\(mx_2\),区间最小值个数\(cnt_{mi}\),区间最大值个数\(cnt_{mx}\)
对于操作\(1\),用一个懒标记\(add\)即可。
对于操作\(2\),若\(mi_1\geq z\),则操作无意义,直接停止。
\(mi_1<z<mi_2\),则区间内所有最小值都变成\(z\),其余数无影响。
直接令区间和加上\(cnt_{mi}*(z-mi_1)\),更新\(mi_1\)\(z\),并打上取\(max\)的标记。
\(z\geq mi_2\),此时无法判断有多少数需要被更改,于是递归下去,直到满足先前条件,然后自下而上更新信息。
这样做的复杂度为\(O(nlogn)\),具体证明要利用势能分析,可自行网上搜索。
操作\(3\)维护方式同理。
现在共有\(add,Min,Max\)三个懒标记,讨论三者优先级,认为\(add\)标记是最优先的,\(Min,Max\)标记地位平等。
对于区间取最值,可能会对其他值和标记产生影响,注意特判,具体见代码。

#include <bits/stdc++.h>
#define int long long

namespace IO {
    inline void read(int &a) {
        int sym=1,num=0;
        char c=getchar();
        while (c<'0' || c>'9') {
            if (c=='-') {
                sym=-1;
        }
        c=getchar();
        }
        while (c>='0' && c<='9') {
            num=num*10+c-'0';
            c=getchar();
        }
        a=sym*num;
    }
    inline void write(int a) {
        if (a<0) {
            putchar('-');
            a*=-1;
        }
        if (a>=10) {
            write(a/10);
        }
        putchar(a%10+'0');
    }
}

using IO::read;
using IO::write;
using namespace std;

const int N=5e5+10,INF=1e9;

struct SegmentTree{
	struct Node{
		int l,r,sum;
		int mi_1,mi_2,cnt_mi;
		int mx_1,mx_2,cnt_mx;
		int tag_mi,tag_mx,tag_add;
	}tr[4*N];
	void pushup(int u){
		tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
		
		tr[u].mi_1=min(tr[u<<1].mi_1,tr[u<<1|1].mi_1);
		
		tr[u].cnt_mi=0;
		if(tr[u<<1].mi_1==tr[u].mi_1) tr[u].cnt_mi+=tr[u<<1].cnt_mi;
		if(tr[u<<1|1].mi_1==tr[u].mi_1) tr[u].cnt_mi+=tr[u<<1|1].cnt_mi;
		
		tr[u].mx_1=max(tr[u<<1].mx_1,tr[u<<1|1].mx_1);
		
		tr[u].cnt_mx=0;
		if(tr[u<<1].mx_1==tr[u].mx_1) tr[u].cnt_mx+=tr[u<<1].cnt_mx;
		if(tr[u<<1|1].mx_1==tr[u].mx_1) tr[u].cnt_mx+=tr[u<<1|1].cnt_mx;
		
		tr[u].mi_2=INF;
		if(tr[u<<1].mi_1!=tr[u].mi_1&&tr[u<<1].mi_1<tr[u].mi_2) tr[u].mi_2=tr[u<<1].mi_1;
		if(tr[u<<1].mi_2!=tr[u].mi_1&&tr[u<<1].mi_2<tr[u].mi_2) tr[u].mi_2=tr[u<<1].mi_2;
		if(tr[u<<1|1].mi_1!=tr[u].mi_1&&tr[u<<1|1].mi_1<tr[u].mi_2) tr[u].mi_2=tr[u<<1|1].mi_1;
		if(tr[u<<1|1].mi_2!=tr[u].mi_1&&tr[u<<1|1].mi_2<tr[u].mi_2) tr[u].mi_2=tr[u<<1|1].mi_2;
		
		tr[u].mx_2=-INF;
		if(tr[u<<1].mx_1!=tr[u].mx_1&&tr[u<<1].mx_1>tr[u].mx_2) tr[u].mx_2=tr[u<<1].mx_1;
		if(tr[u<<1].mx_2!=tr[u].mx_1&&tr[u<<1].mx_2>tr[u].mx_2) tr[u].mx_2=tr[u<<1].mx_2;
		if(tr[u<<1|1].mx_1!=tr[u].mx_1&&tr[u<<1|1].mx_1>tr[u].mx_2) tr[u].mx_2=tr[u<<1|1].mx_1;
		if(tr[u<<1|1].mx_2!=tr[u].mx_1&&tr[u<<1|1].mx_2>tr[u].mx_2) tr[u].mx_2=tr[u<<1|1].mx_2;
	}
	void change_add(int u,int tag_add){
		tr[u].sum+=(tr[u].r-tr[u].l+1)*tag_add;
		tr[u].mi_1+=tag_add;
		if(tr[u].mi_2!=INF) tr[u].mi_2+=tag_add;
		tr[u].mx_1+=tag_add;
		if(tr[u].mx_2!=-INF) tr[u].mx_2+=tag_add;
		if(tr[u].tag_mi!=INF) tr[u].tag_mi+=tag_add;
		if(tr[u].tag_mx!=-INF) tr[u].tag_mx+=tag_add;
		tr[u].tag_add+=tag_add;
	}
	void change_min(int u,int tag_min){
		if(tr[u].mx_1<=tag_min) return;
		tr[u].sum+=tr[u].cnt_mx*(tag_min-tr[u].mx_1);
		if(tr[u].mi_2==tr[u].mx_1) tr[u].mi_2=tag_min;
		if(tr[u].mi_1==tr[u].mx_1) tr[u].mi_1=tag_min;
		if(tr[u].tag_mx>tag_min) tr[u].tag_mx=tag_min;
		tr[u].mx_1=tag_min,tr[u].tag_mi=tag_min;
	}
	void change_max(int u,int tag_max){
		if(tr[u].mi_1>=tag_max) return;
		tr[u].sum+=tr[u].cnt_mi*(tag_max-tr[u].mi_1);
		if(tr[u].mx_2==tr[u].mi_1) tr[u].mx_2=tag_max;
		if(tr[u].mx_1==tr[u].mi_1) tr[u].mx_1=tag_max;
		if(tr[u].tag_mi<tag_max) tr[u].tag_mi=tag_max;
		tr[u].mi_1=tag_max,tr[u].tag_mx=tag_max;
	}
	void pushdown(int u){
		if(tr[u].tag_add) change_add(u<<1,tr[u].tag_add),change_add(u<<1|1,tr[u].tag_add);
		if(tr[u].tag_mi!=INF) change_min(u<<1,tr[u].tag_mi),change_min(u<<1|1,tr[u].tag_mi);
		if(tr[u].tag_mx!=-INF) change_max(u<<1,tr[u].tag_mx),change_max(u<<1|1,tr[u].tag_mx);
		tr[u].tag_add=0,tr[u].tag_mi=INF,tr[u].tag_mx=-INF;
	}
	void build(int u,int l,int r){
		tr[u]={l,r,0,INF,INF,0,-INF,-INF,0,INF,-INF,0};
		if(l==r){
			int x;
			read(x);
			tr[u].sum=tr[u].mi_1=tr[u].mx_1=x;
			tr[u].cnt_mi=tr[u].cnt_mx=1;
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		pushup(u);
	}
	void modify_add(int u,int l,int r,int tag_add){
		if(tr[u].l>=l&&tr[u].r<=r){
			change_add(u,tag_add);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_add(u<<1,l,r,tag_add);
		if(r>mid) modify_add(u<<1|1,l,r,tag_add);
		pushup(u);
	}
	void modify_min(int u,int l,int r,int tag_min){
		if(tr[u].mx_1<=tag_min) return;
		if(tr[u].l>=l&&tr[u].r<=r&&tr[u].mx_2<tag_min){
			change_min(u,tag_min);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_min(u<<1,l,r,tag_min);
		if(r>mid) modify_min(u<<1|1,l,r,tag_min);
		pushup(u);
	}
	void modify_max(int u,int l,int r,int tag_max){
		if(tr[u].mi_1>=tag_max) return;
		if(tr[u].l>=l&&tr[u].r<=r&&tr[u].mi_2>tag_max){
			change_max(u,tag_max);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_max(u<<1,l,r,tag_max);
		if(r>mid) modify_max(u<<1|1,l,r,tag_max);
		pushup(u);
	}
	int query_sum(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,sum=0;
		if(l<=mid) sum+=query_sum(u<<1,l,r);
		if(r>mid) sum+=query_sum(u<<1|1,l,r);
		return sum;
	}
	int query_min(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].mi_1;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,mi=INF;
		if(l<=mid) mi=min(mi,query_min(u<<1,l,r));
		if(r>mid) mi=min(mi,query_min(u<<1|1,l,r));
		return mi;
	}
	int query_max(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].mx_1;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,mx=-INF;
		if(l<=mid) mx=max(mx,query_max(u<<1,l,r));
		if(r>mid) mx=max(mx,query_max(u<<1|1,l,r));
		return mx;
	}
}tree;

signed main(){
	int n,m;
	read(n);
	tree.build(1,1,n);
	read(m);
	while(m--){
		int tp,x,y,z;
		read(tp),read(x),read(y);
		if(tp<=3) read(z);
		if(tp==1) tree.modify_add(1,x,y,z);
		if(tp==2) tree.modify_max(1,x,y,z);
		if(tp==3) tree.modify_min(1,x,y,z);
		if(tp==4) write(tree.query_sum(1,x,y)),puts("");
		if(tp==5) write(tree.query_max(1,x,y)),puts("");
		if(tp==6) write(tree.query_min(1,x,y)),puts("");
	}
	return 0;
}

先从一道最简单的板子题入手
\(P4314\)
https://www.luogu.com.cn/problem/P4314
题意:给定一个序列,维护四种操作:
\(1\):给定\(x,y\),询问当前\([x,y]\)内最大值。
\(2\):给定\(x,y\),询问历史\([x,y]\)内最大值。
\(3\):给定\(x,y,z\),使\([x,y]\)内所有数加\(z\)
\(4\):给定\(x,y,z\),使\([x,y]\)内所有数变成\(z\)
题解:如果没有操作\(2\),那么用两个懒标记\(add,cov\)简单维护即可。
本题考查了\(lazy\ tag\)与树上节点的更新顺序的关系,通透理解此题,方可称的上真正入门线段树。
\(lazy\ tag\)实际上可以看作是对于此节点表示的区间的操作序列,这便是线段树的内核。
\(pushdown\)操作实际上是将父节点的操作序列接在儿子节点操作序列之后。
对一个点\(pushdown\)之后,该节点的操作序列被清空,因为在递归完子树后,该点答案将被更新。
那么最重要的部分,就是针对操作序列的关系处理,也就是代码中\(pushdown\)内繁琐的标记间的互相影响。
本题维护的操作序列仍然只有加操作和赋值操作,且相邻同种操作可合并,于是操作序列一定形如加操作,赋值操作,加操作,...。
若直接对每个节点维护操作序列,那么得到了\(O(n)\)\(pushdown\),显然不可接受。
那么问题的核心,就是设法去压缩操作序列,使得可用较小空间,存储相同的信息。
对于本题来说,若对区间进行一次赋值操作后,所有的操作都可以转换为赋值操作。
于是操作序列只用存储加操作,赋值操作,到此为止,本题核心问题得到解决。
但仍然需要解决历史最值问题,只需对所有标记,全部增加一维历史信息即可,具体见代码。

#include <bits/stdc++.h>
#define int long long
	
using namespace std;

const int N=1e5+10;

struct SegmentTree{
	struct Node{
		int l,r;
		int mx,his_mx;
		int add,his_add;
		int cov,his_cov;
		bool flag;
	}tr[4*N];
	void pushup(int u){
		tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx);
		tr[u].his_mx=max(tr[u<<1].his_mx,tr[u<<1|1].his_mx);
	}
	void change_add(int u,int add,int his_add){
		if(tr[u].flag){
			tr[u].his_cov=max(tr[u].his_cov,tr[u].cov+his_add);
			tr[u].his_mx=max(tr[u].his_mx,tr[u].mx+his_add);
			tr[u].cov+=add,tr[u].mx+=add;
		}
		else{
			tr[u].his_add=max(tr[u].his_add,tr[u].add+his_add);
			tr[u].his_mx=max(tr[u].his_mx,tr[u].mx+his_add);
			tr[u].add+=add,tr[u].mx+=add;
		}
	}
	void change_cov(int u,int cov,int his_cov){
		if(tr[u].flag){
			tr[u].his_cov=max(tr[u].his_cov,his_cov);
			tr[u].his_mx=max(tr[u].his_mx,his_cov);
		}
		else{
			tr[u].flag=1;
			tr[u].his_cov=his_cov;
			tr[u].his_mx=max(tr[u].his_mx,his_cov);
		}
		tr[u].cov=tr[u].mx=cov;
	}
	void pushdown(int u){
		change_add(u<<1,tr[u].add,tr[u].his_add);
		change_add(u<<1|1,tr[u].add,tr[u].his_add);
		tr[u].add=tr[u].his_add=0;
		if(tr[u].flag){
			change_cov(u<<1,tr[u].cov,tr[u].his_cov);
			change_cov(u<<1|1,tr[u].cov,tr[u].his_cov);
			tr[u].cov=tr[u].his_cov=tr[u].flag=0;
		}
	}
	void build(int u,int l,int r){
		tr[u]={l,r};
		if(l==r){
			int x;
			cin >> x;
			tr[u].mx=tr[u].his_mx=x;
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		pushup(u);
	}
	void modify_add(int u,int l,int r,int add){
		if(tr[u].l>=l&&tr[u].r<=r){
			change_add(u,add,add);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_add(u<<1,l,r,add);
		if(r>mid) modify_add(u<<1|1,l,r,add);
		pushup(u);
	}
	void modify_cov(int u,int l,int r,int cov){
		if(tr[u].l>=l&&tr[u].r<=r){
			change_cov(u,cov,cov);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_cov(u<<1,l,r,cov);
		if(r>mid) modify_cov(u<<1|1,l,r,cov);
		pushup(u);
	}
	int query_mx(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].mx;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,mx=-1e18;
		if(l<=mid) mx=max(mx,query_mx(u<<1,l,r));
		if(r>mid) mx=max(mx,query_mx(u<<1|1,l,r));
		return mx;
	}
	int query_his_mx(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].his_mx;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,his_mx=-1e18;
		if(l<=mid) his_mx=max(his_mx,query_his_mx(u<<1,l,r));
		if(r>mid) his_mx=max(his_mx,query_his_mx(u<<1|1,l,r));
		return his_mx;
	}
}tree;

signed main(){
	int n,m;
	cin >> n;
	tree.build(1,1,n);
	cin >> m;
	while(m--){
		char op;
		int x,y,z;
		cin >> op >> x >> y;
		if(op!='Q'&&op!='A') cin >> z;
		if(op=='Q') cout << tree.query_mx(1,x,y) << endl;
		if(op=='A') cout << tree.query_his_mx(1,x,y) << endl;
		if(op=='P') tree.modify_add(1,x,y,z);
		if(op=='C') tree.modify_cov(1,x,y,z);
	}
	return 0;
} 

线段树区间最值操作同历史信息结合。
\(P6242\)
https://www.luogu.com.cn/problem/P6242
题意:给定一个序列,维护五种操作:
\(1\):给定\(x,y,z\),将\([x,y]\)所有数加\(z\)
\(2\):给定\(x,y,z\),将\([x,y]\)所有数同\(z\)\(min\)
\(3\):给定\(x,y\),回答\([x,y]\)所有数的和。
\(4\):给定\(x,y\),求\([x,y]\)的最大值。
\(5\):给定\(x,y\),求\([x,y]\)的历史最大值。
题解:结合以上两题的维护方式,但根据最值操作原理,一个区间内数的修改情况是离散的。
于是考虑分开维护最大值\(/\)非最大值,的区间当前加\(/\)区间历史最大加,共四个懒标记即可,具体看代码。

#include <bits/stdc++.h>
#define int long long

namespace IO {
    inline void read(int &a) {
        int sym=1,num=0;
        char c=getchar();
        while (c<'0' || c>'9') {
            if (c=='-') {
                sym=-1;
        }
        c=getchar();
        }
        while (c>='0' && c<='9') {
            num=num*10+c-'0';
            c=getchar();
        }
        a=sym*num;
    }
    inline void write(int a) {
        if (a<0) {
            putchar('-');
            a*=-1;
        }
        if (a>=10) {
            write(a/10);
        }
        putchar(a%10+'0');
    }
}

using IO::read;
using IO::write;
using namespace std;

const int N=5e5+10,INF=1e18;

struct SegmentTree{
	struct Node{
		int l,r,sum;
		int mx_1,his_mx_1;
		int mx_2,cnt_mx;
		int add_1,his_add_1;
		int add_2,his_add_2;
	}tr[4*N];
	void pushup(int u){
		tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;	
		tr[u].mx_1=max(tr[u<<1].mx_1,tr[u<<1|1].mx_1);
		tr[u].his_mx_1=max(tr[u<<1].his_mx_1,tr[u<<1|1].his_mx_1);
		tr[u].cnt_mx=0;
		if(tr[u<<1].mx_1==tr[u].mx_1) tr[u].cnt_mx+=tr[u<<1].cnt_mx;
		if(tr[u<<1|1].mx_1==tr[u].mx_1) tr[u].cnt_mx+=tr[u<<1|1].cnt_mx;
		
		tr[u].mx_2=-INF;
		if(tr[u<<1].mx_1!=tr[u].mx_1&&tr[u<<1].mx_1>tr[u].mx_2) tr[u].mx_2=tr[u<<1].mx_1;
		if(tr[u<<1].mx_2!=tr[u].mx_1&&tr[u<<1].mx_2>tr[u].mx_2) tr[u].mx_2=tr[u<<1].mx_2;
		if(tr[u<<1|1].mx_1!=tr[u].mx_1&&tr[u<<1|1].mx_1>tr[u].mx_2) tr[u].mx_2=tr[u<<1|1].mx_1;
		if(tr[u<<1|1].mx_2!=tr[u].mx_1&&tr[u<<1|1].mx_2>tr[u].mx_2) tr[u].mx_2=tr[u<<1|1].mx_2;
	}
	void change(int u,int add_1,int his_add_1,int add_2,int his_add_2){
		tr[u].sum+=tr[u].cnt_mx*add_1+(tr[u].r-tr[u].l+1-tr[u].cnt_mx)*add_2;
		tr[u].his_mx_1=max(tr[u].his_mx_1,tr[u].mx_1+his_add_1);
		tr[u].his_add_1=max(tr[u].his_add_1,tr[u].add_1+his_add_1);
		tr[u].his_add_2=max(tr[u].his_add_2,tr[u].add_2+his_add_2);
		//tr[u].his_mx_1=max(tr[u].his_mx_1,tr[u].mx_2+his_add_2);
		tr[u].mx_1+=add_1,tr[u].add_1+=add_1,tr[u].add_2+=add_2;
		if(tr[u].mx_2!=-INF) tr[u].mx_2+=add_2;
	}
	void pushdown(int u){
		int mx=max(tr[u<<1].mx_1,tr[u<<1|1].mx_1);
		if(tr[u<<1].mx_1==mx) change(u<<1,tr[u].add_1,tr[u].his_add_1,tr[u].add_2,tr[u].his_add_2);
		else change(u<<1,tr[u].add_2,tr[u].his_add_2,tr[u].add_2,tr[u].his_add_2);
		if(tr[u<<1|1].mx_1==mx) change(u<<1|1,tr[u].add_1,tr[u].his_add_1,tr[u].add_2,tr[u].his_add_2);
		else change(u<<1|1,tr[u].add_2,tr[u].his_add_2,tr[u].add_2,tr[u].his_add_2);
		tr[u].add_1=tr[u].his_add_1=tr[u].add_2=tr[u].his_add_2=0;
	}
	void build(int u,int l,int r){
		tr[u]={l,r,0,-INF,-INF,-INF,0,0,0,0,0};
		if(l==r){
			int x;
			read(x);
			tr[u].sum=tr[u].mx_1=tr[u].his_mx_1=x;
			tr[u].cnt_mx=1;
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		pushup(u);
	}
	void modify_add(int u,int l,int r,int add){
		if(tr[u].l>=l&&tr[u].r<=r){
			change(u,add,add,add,add);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_add(u<<1,l,r,add);
		if(r>mid) modify_add(u<<1|1,l,r,add);
		pushup(u);
	}
	void modify_min(int u,int l,int r,int mi){
		if(tr[u].mx_1<=mi) return;
		if(tr[u].l>=l&&tr[u].r<=r&&tr[u].mx_2<mi){
			change(u,mi-tr[u].mx_1,mi-tr[u].mx_1,0,0);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_min(u<<1,l,r,mi);
		if(r>mid) modify_min(u<<1|1,l,r,mi);
		pushup(u);
	}
	int query_sum(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,sum=0;
		if(l<=mid) sum+=query_sum(u<<1,l,r);
		if(r>mid) sum+=query_sum(u<<1|1,l,r);
		return sum;
	}
	int query_max(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].mx_1;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,mx=-INF;
		if(l<=mid) mx=max(mx,query_max(u<<1,l,r));
		if(r>mid) mx=max(mx,query_max(u<<1|1,l,r));
		return mx;
	}
	int query_his_max(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].his_mx_1;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,mx=-INF;
		if(l<=mid) mx=max(mx,query_his_max(u<<1,l,r));
		if(r>mid) mx=max(mx,query_his_max(u<<1|1,l,r));
		return mx;
	}
}tree;

signed main(){
	int n,m;
	read(n),read(m);
	tree.build(1,1,n);
	while(m--){
		int tp,x,y,z;
		read(tp),read(x),read(y);
		if(tp<=2) read(z);
		if(tp==1) tree.modify_add(1,x,y,z);
		if(tp==2) tree.modify_min(1,x,y,z);
		if(tp==3) write(tree.query_sum(1,x,y)),puts("");
		if(tp==4) write(tree.query_max(1,x,y)),puts("");
		if(tp==5) write(tree.query_his_max(1,x,y)),puts("");
	}
	return 0;
}

一道区间最值操作例题
\(P9631\)
https://www.luogu.com.cn/problem/P9631
题意:给定\(n\)堆石子,维护两种操作:
\(1\):给出\(x,y,z\),将\([x,y]\)中所有堆与\(z\)\(max\)
\(2\):给出\(x,y,z\),求\([x,y]\)堆和一个石子数为\(z\)的石堆进行\(Nim\)游戏,求出第一次先手取完石子后,先手必胜的方案数。
题解:先手必败等价于剩余石堆异或和为\(0\)
若当前异或和为\(0\),则任意操作都会得到不为\(0\)局面,方案数为\(0\)
否则,设当前异或和为\(s\),则若在第\(i\)里取,应取\(a_i-s\bigoplus a_i\)个石子。
则所有方案数即为\(\sum_{i=l}^{r}[a_i\geq s\bigoplus a_i]\)
考虑异或本质是不进位加法,所以考虑\(s\)的最高位\(1\),若\(a_i\)此位为\(1\),有\(a_i\geq s\bigoplus a_i\),反之不成立。
于是维护区间异或和,以及区间每一个二进制\(1\)个数,操作\(1\)用传统区间最值操作维护即可。

#include <bits/stdc++.h>

using namespace std;

const int N=2e5+10,INF=2e9;

struct SegmentTree{
	struct Node{
		int l,r;
		int mi_1,mi_2,cnt_mi;
		int s,tag_mx;
		int cnt[31];
	}tr[4*N];
	void pushup(int u){
		tr[u].mi_1=min(tr[u<<1].mi_1,tr[u<<1|1].mi_1);
		tr[u].cnt_mi=0;
		if(tr[u<<1].mi_1==tr[u].mi_1) tr[u].cnt_mi+=tr[u<<1].cnt_mi;
		if(tr[u<<1|1].mi_1==tr[u].mi_1) tr[u].cnt_mi+=tr[u<<1|1].cnt_mi;
		tr[u].mi_2=INF;
		if(tr[u<<1].mi_1!=tr[u].mi_1) tr[u].mi_2=min(tr[u].mi_2,tr[u<<1].mi_1);
		if(tr[u<<1].mi_2!=tr[u].mi_1) tr[u].mi_2=min(tr[u].mi_2,tr[u<<1].mi_2);
		if(tr[u<<1|1].mi_1!=tr[u].mi_1) tr[u].mi_2=min(tr[u].mi_2,tr[u<<1|1].mi_1);
		if(tr[u<<1|1].mi_2!=tr[u].mi_1) tr[u].mi_2=min(tr[u].mi_2,tr[u<<1|1].mi_2);
		tr[u].s=tr[u<<1].s^tr[u<<1|1].s;
		for(int i=0; i<=30; i++) tr[u].cnt[i]=tr[u<<1].cnt[i]+tr[u<<1|1].cnt[i];
	}
	void build(int u,int l,int r){
		tr[u].l=l,tr[u].r=r;
		if(l==r){
			int x;
			cin >> x;
			tr[u].cnt_mi=1;
			tr[u].mi_1=tr[u].s=x;
			tr[u].mi_2=INF,tr[u].tag_mx=-INF;
			memset(tr[u].cnt,0,sizeof tr[u].cnt);
			for(int i=0; i<=30; i++) tr[u].cnt[i]+=x>>i&1;
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		pushup(u);
	}
	void change(int u,int tag_mx){
		if(tr[u].mi_1>=tag_mx) return;
		if(tr[u].cnt_mi%2) tr[u].s^=tr[u].mi_1^tag_mx;
		for(int i=0; i<=30; i++)
			tr[u].cnt[i]+=tr[u].cnt_mi*((tag_mx>>i&1)-(tr[u].mi_1>>i&1));
		tr[u].mi_1=tag_mx;
		tr[u].tag_mx=tag_mx;
	}
	void pushdown(int u){
		change(u<<1,tr[u].tag_mx);
		change(u<<1|1,tr[u].tag_mx);
		tr[u].tag_mx=-INF;
	}
	void modify(int u,int l,int r,int tag_mx){
		if(tr[u].mi_1>=tag_mx) return;
		if(tr[u].l>=l&&tr[u].r<=r&&tr[u].mi_2>tag_mx){
			change(u,tag_mx);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify(u<<1,l,r,tag_mx);
		if(r>mid) modify(u<<1|1,l,r,tag_mx);
		pushup(u);
	}
	int query_s(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].s;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,s=0;
		if(l<=mid) s^=query_s(u<<1,l,r);
		if(r>mid) s^=query_s(u<<1|1,l,r);
		return s;
	}
	int query_cnt(int u,int l,int r,int k){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].cnt[k];
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,cnt=0;
		if(l<=mid) cnt+=query_cnt(u<<1,l,r,k);
		if(r>mid) cnt+=query_cnt(u<<1|1,l,r,k);
		return cnt;
	}
}tree;

int main(){
	int n,m;
	cin >> n >> m;
	tree.build(1,1,n);
	while(m--){
		int tp,x,y,z;
		cin >> tp >> x >> y >> z;
		if(tp==1) tree.modify(1,x,y,z);
		else{
			int s=tree.query_s(1,x,y);
			s^=z;
			if(!s){
				cout << 0 << endl;
				continue;
			}
			for(int i=30; i>=0; i--)
				if(s>>i&1){
					int ans=tree.query_cnt(1,x,y,i);
					ans+=z>>i&1;
					cout << ans << endl;
					break;
				}
		}
	}
	return 0;
}

一道历史最值问题例题
\(SP1557\)
https://www.luogu.com.cn/problem/SP1557
题意:给定一个序列,多次询问\([x,y]\),求\([x,y]\)最大子段和,相同权值的数只会被计算一次。
题解:离线询问,从左到右加数,设\(pre(i)\)为前面第一个一个和\(a_i\)相同的位置。
加入一个数\(a_i\)时,对\([pre(i)+1,i]\)进行区间加\(a_i\)
考虑这样做线段树内保存信息的是什么,在时刻\(t\),叶子节点\([i,i],i\geq t\)中,存储的即为去重后区间\([i,t]\)的和。
设版本\(t\)时,叶子节点\([i,i]\)权值为\(val(i,t)\)
则询问\([x,y]\)答案即为\(max_{i=x}^{y}\{max_{j=i}^{y}\{val(i,j)\}\}\)
\(max_{j=i}^{y}\{val(i,j)\}\)正是到时刻\(y\)为止,叶子节点\([i,i]\)出现过的所有值中最大的。
所以答案即为区间\([x,y]\)内所有点的历史最大值。
所以所需操作即为区间加,查询区间历史最大值,用线段树维护即可。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N=1e5+10;

int n,m;
int a[N],ans[N];
map<int,int> pre;

struct Query{
	int l,r,id;
	bool operator <(Query t){
		return r<t.r;
	}
}q[N];

struct SegmentTree{
	struct Node{
		int l,r;
		int mx,his_mx;
		int add,his_add;
	}tr[4*N];
	void pushup(int u){
		tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx);
		tr[u].his_mx=max(tr[u<<1].his_mx,tr[u<<1|1].his_mx);
	}
	void change(int u,int add,int his_add){
		tr[u].his_mx=max(tr[u].his_mx,tr[u].mx+his_add);
		tr[u].his_add=max(tr[u].his_add,tr[u].add+his_add);
		tr[u].mx+=add,tr[u].add+=add;
	}
	void pushdown(int u){
		change(u<<1,tr[u].add,tr[u].his_add);
		change(u<<1|1,tr[u].add,tr[u].his_add);
		tr[u].add=tr[u].his_add=0;
	}
	void build(int u,int l,int r){
		tr[u]={l,r};
		if(l==r) return;
		int mid=l+r>>1;;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
	}
	void modify(int u,int l,int r,int add){
		if(tr[u].l>=l&&tr[u].r<=r){
			change(u,add,add);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify(u<<1,l,r,add);
		if(r>mid) modify(u<<1|1,l,r,add);
		pushup(u);
	}
	int query(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].his_mx;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,mx=0;
		if(l<=mid) mx=max(mx,query(u<<1,l,r));
		if(r>mid) mx=max(mx,query(u<<1|1,l,r));
		return mx;
	}
}tree;

signed main(){
	cin >> n;
	for(int i=1; i<=n; i++) cin >> a[i];
	cin >> m;
	for(int i=1; i<=m; i++){
		int l,r;
		cin >> l >> r;
		q[i]={l,r,i};
	}
	sort(q+1,q+m+1);
	tree.build(1,1,n);
	for(int i=1,j=1; i<=m; i++){
		while(j<=q[i].r){
			tree.modify(1,pre[a[j]]+1,j,a[j]);
			pre[a[j]]=j,j++;
		}
		ans[q[i].id]=tree.query(1,q[i].l,q[i].r);
	}
	for(int i=1; i<=m; i++) cout << ans[i] << endl;
	return 0;
}
posted @ 2025-03-13 18:22  junliang123  阅读(68)  评论(0)    收藏  举报