区间最值操作,区间历史最值
将区间内所有数同某数取\(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;
}
浙公网安备 33010602011771号