众所周知,线段树通过懒标记来做到优化时间复杂度。但使用懒标记必须满足,维护的值可以通过懒标记更新,以及懒标记之间可以快速合并。但有时候我们的区间操作不能简单地依赖懒标记,比如区间开根号,我们需要知道叶子节点的值才能得到开根号的结果,于是我们引入势能线段树这个概念:如果我们发现进行完一个操作之后,总会使能够接受的继续进行的操作次数越来越少,那么我们就可以先在线段树上暴力修改,然后维护是否还能继续操作,如果不能操作了,就直接返回,这样的话我们就达到了优化时间复杂度的效果。
https://www.luogu.com.cn/problem/P4145
题目描述
第一行一个整数 ,代表数列中数的个数。
第二行 个正整数,表示初始状态下数列中的数。
第三行一个整数 ,表示有 次操作。
接下来 行每行三个整数 k l r
。
表示给 中的每个数开平方(下取整)。
表示询问 中各个数的和。
数据中有可能 ,所以遇到这种情况请交换 和 。
数据范围
对于 的数据,,数列中的数不超过 。
对于 的数据,,,数列中的数大于 ,且不超过 。
笔记
突破点:对一个数反复开根号,并不需要太多的次数,就可以使得这个数变成1,并且在这之后再开根号的话数字仍然不变。
于是我们先暴力遍历到叶子节点开根号,但同时维护某个节点所涵盖的区间是否都变成1,如果都变为1的话,就不需要再暴力遍历该区间开根号了。
https://codeforces.com/problemset/problem/438/D
题目描述
给定 个数,三种操作:
数据范围
。其他的数字最大不超过 。
笔记
突破点:让一个数不断对一些数取模,那么这个数应该是越来越小的,至少不会增大。同时如果这个数已经比模数还小了,就不需要再取模了,取模后的结果就是他本身。
于是我们维护区间的最大值,如果这个最大值比模数还小,那么整个的这个区间并不需要取模。反之,我们暴力取模。
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
#define _ 100005
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
typedef pair<int,int> pii;
int n,m,in[_];
struct Node{int sum,mx;} tree[_<<2];
inline void pushup(int k){
nt.sum=lt.sum+rt.sum;
nt.mx=max(lt.mx,rt.mx);
}
void build(int k,int l,int r){
if(l==r) return nt.mx=in[l],nt.sum=in[l],void();
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
int query(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.sum;
int mid=(l+r)>>1,res=0;
if(x<=mid) res+=query(ls(k),l,mid,x,y);
if(y>mid) res+=query(rs(k),mid+1,r,x,y);
return res;
}
void modify(int k,int l,int r,int x,int y,int p){
if(l==r) return nt.sum%=p,nt.mx%=p,void();
if(nt.mx<p) return;
int mid=(l+r)>>1;
if(x<=mid) modify(ls(k),l,mid,x,y,p);
if(y>mid) modify(rs(k),mid+1,r,x,y,p);
pushup(k);
}
void change(int k,int l,int r,int x,int v){
if(l==r) return nt.sum=nt.mx=v,void();
int mid=(l+r)>>1;
if(x<=mid) change(ls(k),l,mid,x,v);
else change(rs(k),mid+1,r,x,v);
pushup(k);
}
inline void subtask(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>in[i];
build(1,1,n);
while(m--){
int op,x,y,z;cin>>op>>x>>y;
if(op==1) cout<<query(1,1,n,x,y)<<endl;
else if(op==2) cin>>z,modify(1,1,n,x,y,z);
else if(op==3) change(1,1,n,x,y);
}
}
signed main(){
ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
https://codeforces.com/contest/920/problem/F
题目描述
设 为 的因子个数。现在给定 个数,有两种操作:
数据范围
,
笔记
突破点:和第一道题目一样的道理,修改操作会让数字不断的减小,直到数字变为 或 ,于是修改操作仍然可以暴力处理。
由于数字的值域只有 ,可以先预处理一把。
cpp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define _ 1000006
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
typedef pair<int,int> pii;
int prime[_],cnt,vis[_],f[_],m,n,in[_];
struct Node{int sum,ok;} tree[_<<2];
inline void init(){
f[1]=1;
for(int i=2;i<=1000000;i++){
f[i]=1;
if(!vis[i]) prime[++cnt]=i,vis[i]=i;
for(int j=1;j<=cnt&&prime[j]*i<=1000000;j++){
vis[i*prime[j]]=prime[j];
if(i%prime[j]==0) break;
}
}
for(int i=2;i<=1000000;i++){
int x=i;
while(x!=1){
int c=0;
int nxt=vis[x];
while(x%nxt==0) x/=nxt,c++;
f[i]*=(1+c);
}
}
}
void pushup(int k){
nt.sum=lt.sum+rt.sum;
nt.ok=lt.ok&rt.ok;
}
void build(int k,int l,int r){
if(l==r) return nt.sum=in[l],nt.ok=in[l]<=2?1:0,void();
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
int query(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.sum;
int mid=(l+r)>>1,res=0;
if(x<=mid) res+=query(ls(k),l,mid,x,y);
if(y>mid) res+=query(rs(k),mid+1,r,x,y);
return res;
}
void modify(int k,int l,int r,int x,int y){
if(nt.ok) return;
if(l==r) return nt.ok=f[nt.sum]<=2?1:0,nt.sum=f[nt.sum],void();
int mid=(l+r)>>1;
if(x<=mid) modify(ls(k),l,mid,x,y);
if(y>mid) modify(rs(k),mid+1,r,x,y);
pushup(k);
}
inline void subtask(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>in[i];
build(1,1,n);
while(m--){
int op,l,r;cin>>op>>l>>r;
if(op==1) modify(1,1,n,l,r);
else if(op==2) cout<<query(1,1,n,l,r)<<endl;
}
}
signed main(){
init();
// for(int i=1;i<=20;i++){
// cout<<i<<' '<<f[i]<<endl;
// }
ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
https://codeforces.com/gym/103107/problem/A
题目描述
给定 个数,有三种操作:
数据范围
笔记
突破点:按位与其实也是有“单调性”的。对于一个数字,对其不断进行按位与,实际上就是不断将其二进制为 的位上变为 , 的数量是单调不增的。如果将数字 与某个数字 按位与, 二进制为 的位置, 相同位置上都为 ,那么这个按位与实际上是没有效果的。
对于一个区间中的多个数也有上述的效果。我们令 ,即把区间内所有数字都按位或起来。然后如果 那么我们就说这个区间的数字就都没必要再对 按位与了。
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117#include<bits/stdc++.h>
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define _ 400005
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
#define LOCAL
namespace ly
{
namespace IO
{
#ifndef LOCAL
constexpr auto maxn=1<<20;
char in[maxn],out[maxn],*p1=in,*p2=in,*p3=out;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,maxn,stdin),p1==p2)?EOF:*p1++)
#define flush() (fwrite(out,1,p3-out,stdout))
#define putchar(x) (p3==out+maxn&&(flush(),p3=out),*p3++=(x))
class Flush{public:~Flush(){flush();}}_;
#endif
namespace usr
{
template<typename type>
inline type read(type &x)
{
x=0;bool flag(0);char ch=getchar();
while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return flag?x=-x:x;
}
template<typename type>
inline void write(type x)
{
x<0?x=-x,putchar('-'):0;
static short Stack[50],top(0);
do Stack[++top]=x%10,x/=10;while(x);
while(top) putchar(Stack[top--]|48);
}
inline char read(char &x){do x=getchar();while(isspace(x));return x;}
inline char write(const char &x){return putchar(x);}
inline void read(char *x){static char ch;read(ch);do *(x++)=ch;while(!isspace(ch=getchar())&&~ch);}
template<typename type>inline void write(type *x){while(*x)putchar(*(x++));}
inline void read(string &x){static char ch;read(ch),x.clear();do x+=ch;while(!isspace(ch=getchar())&&~ch);}
inline void write(const string &x){for(int i=0,len=x.length();i<len;++i)putchar(x[i]);}
template<typename type,typename...T>inline void read(type &x,T&...y){read(x),read(y...);}
template<typename type,typename...T>
inline void write(const type &x,const T&...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}
template<typename type>
inline void put(const type &x,bool flag=1){write(x),flag?putchar('\n'):putchar(' ');}
}
#ifndef LOCAL
#undef getchar
#undef flush
#undef putchar
#endif
}using namespace IO::usr;
}using namespace ly::IO::usr;
typedef pair<int,int> pii;
int n,m,in[_];
struct Node{int mx,e;} tree[_<<2];
void pushup(int k){
nt.e=lt.e|rt.e;
nt.mx=max(lt.mx,rt.mx);
}
void build(int k,int l,int r){
if(l==r) return nt.e=nt.mx=in[l],void();
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
void modify(int k,int l,int r,int x,int y,int v){
if((nt.e&v)==nt.e) return;
if(l==r) return nt.mx&=v,nt.e&=v,void();
int mid=(l+r)>>1;
if(x<=mid) modify(ls(k),l,mid,x,y,v);
if(y>mid) modify(rs(k),mid+1,r,x,y,v);
pushup(k);
}
void change(int k,int l,int r,int x,int v){
if(l==r) return nt.mx=v,nt.e=v,void();
int mid=(l+r)>>1;
if(x<=mid) change(ls(k),l,mid,x,v);
else change(rs(k),mid+1,r,x,v);
pushup(k);
}
int query(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.mx;
int mid=(l+r)>>1,res=-inf;
if(x<=mid) res=max(res,query(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query(rs(k),mid+1,r,x,y));
return res;
}
inline void subtask(){
read(n,m);
for(int i=1;i<=n;i++) read(in[i]);
build(1,1,n);
while(m--){
char op[4];int x,y,z;
read(op,x,y);
if(op[0]=='A') read(z),modify(1,1,n,x,y,z);
else if(op[0]=='U') change(1,1,n,x,y);
else if(op[0]=='Q') put(query(1,1,n,x,y));
}
}
signed main(){
//ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
https://vjudge.net/problem/HDU-7059
题目描述
给定 个数,三种操作:
数据范围
,且
笔记
对于操作 2 和 操作 3 都不会增加数字二进制 1 的个数,且操作 2 还会减少二进制 1 的个数,所以对于操作 2 我们可以考虑暴力。但注意,操作 3 也暴力的话时间复杂度就不对了,因为操作 3 并不会减少二进制 1 的个数。我们考虑能不能通过懒标记来优化。
我们把一个数的最高位和其他位分开,即 ,然后操作 3 就相当于把区间内所有的 都乘 ,这就可以使用懒标记了
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293//#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=998244353;
typedef pair<int,int> pii;
#define _ 100005
struct Node{int high,low,tag;} tree[_<<2];
int in[_],pw[_];
inline int lowbit(int x){return x&-x;}
inline void pushup(int k){nt.high=(lt.high+rt.high)%mod;nt.low=(lt.low+rt.low)%mod;}
inline void pushdown(int k){
if(!nt.tag) return;
lt.high=lt.high*pw[nt.tag]%mod;
rt.high=rt.high*pw[nt.tag]%mod;
lt.tag+=nt.tag;
rt.tag+=nt.tag;
nt.tag=0;
}
void build(int k,int l,int r){
nt.tag=0;
if(l==r){
int now=0,x=in[l];
while(x){
if(x&1) nt.high=now;
x>>=1;now++;
}
nt.high=pw[nt.high];nt.low=in[l]-nt.high;
nt.tag=0;
return;
}
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
int query(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return (nt.low+nt.high)%mod;
int mid=(l+r)>>1,res=0;
pushdown(k);
if(x<=mid) res+=query(ls(k),l,mid,x,y),res%=mod;
if(y>mid) res+=query(rs(k),mid+1,r,x,y),res%=mod;
return res;
}
void modify_lowbit(int k,int l,int r,int x,int y){
if(!nt.high&&!nt.low) return void();
if(l==r){
if(nt.low) nt.low-=lowbit(nt.low);
else nt.high=0;
return;
}
int mid=(l+r)>>1;
pushdown(k);
if(x<=mid) modify_lowbit(ls(k),l,mid,x,y);
if(y>mid) modify_lowbit(rs(k),mid+1,r,x,y);
pushup(k);
}
void modify_highbit(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.high=nt.high*2%mod,nt.tag++,void();
int mid=(l+r)>>1;
pushdown(k);
if(x<=mid) modify_highbit(ls(k),l,mid,x,y);
if(y>mid) modify_highbit(rs(k),mid+1,r,x,y);
pushup(k);
}
inline void subtask(){
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>in[i];
build(1,1,n);
int m;cin>>m;
while(m--){
int op,l,r;cin>>op>>l>>r;
if(op==1) cout<<query(1,1,n,l,r)<<endl;
if(op==2) modify_lowbit(1,1,n,l,r);
if(op==3) modify_highbit(1,1,n,l,r);
}
}
signed main(){
pw[0]=1;
for(int i=1;i<=100000;i++) pw[i]=pw[i-1]*2%mod;
ios::sync_with_stdio(false);
int t;cin>>t;
while(t--) subtask();
return 0;
}
https://vjudge.net/problem/HDU-7116
题目描述
给定 个数,两种操作:
数据范围
笔记
我们发现,一个数 加上 ,那么这个数的二进制 1 数量要么减少要么不变。如果 的二进制 1 数量为 1 了,那么 就相当于对 乘二了。
那么就有了做法,区间内所有的数的二进制 1 数量都为 1 了,那么就相当于区间乘 2 ,使用懒标记。反之,暴力修改
cpp1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=998244353;
typedef pair<int,int> pii;
#define _ 100005
struct Node{int sum,ok,tag;} tree[_<<2];
int in[_],pw[_];
inline int lowbit(int x){return x&-x;}
inline int popcnt(int x,int res=0){for(;x;x>>=1) if(x&1) res++;return res;}
inline void pushup(int k){nt.sum=(lt.sum+rt.sum)%mod;nt.ok=lt.ok&rt.ok;}
inline void pushdown(int k){
if(!nt.tag) return;
lt.sum=lt.sum*pw[nt.tag]%mod;
rt.sum=rt.sum*pw[nt.tag]%mod;
lt.tag+=nt.tag;
rt.tag+=nt.tag;
nt.tag=0;
}
void build(int k,int l,int r){
nt.tag=0;
if(l==r) return nt.sum=in[l],nt.ok=popcnt(in[l])==1?1:0,void();
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
int query(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.sum;
pushdown(k);
int mid=(l+r)>>1,res=0;
if(x<=mid) res+=query(ls(k),l,mid,x,y),res%=mod;
if(y>mid) res+=query(rs(k),mid+1,r,x,y),res%=mod;
return res;
}
void modify(int k,int l,int r,int x,int y){
if(l>=x&&r<=y&&nt.ok) return nt.sum=nt.sum*2%mod,nt.tag++,void();
if(l==r) return nt.sum+=lowbit(nt.sum),nt.ok=popcnt(nt.sum)==1?1:0,void();
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid) modify(ls(k),l,mid,x,y);
if(y>mid) modify(rs(k),mid+1,r,x,y);
pushup(k);
}
inline void subtask(){
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>in[i];
build(1,1,n);
int m;cin>>m;
while(m--){
int op,l,r;cin>>op>>l>>r;
if(op==1) modify(1,1,n,l,r);
if(op==2) cout<<query(1,1,n,l,r)<<endl;
}
}
signed main(){
pw[0]=1;
for(int i=1;i<=100000;i++) pw[i]=pw[i-1]*2%mod;
ios::sync_with_stdio(false);
int t;cin>>t;
while(t--) subtask();
return 0;
}
我们把一类需要划分数域来解决的区间最值、区间历史最值问题,用到的势能线段树称为吉司机线段树
以一道题举例
Gorgeous Sequence
https://vjudge.net/problem/HDU-5306
题目描述
给出一个长度为 的序列 和 次操作,每次操作为以下三种类型之一:
笔记
如果没有区间求和的话就很好做了。有区间求和的话我们就不能使用传统的懒标记的方法来做了。
这种题一般是这样考虑的:
维护区间最大值 和次大值 ,以及最大值个数 ,区间和 ,然后进行区间对 取 的时候,有如下的情况:
时间复杂度是正确的,我们这么考虑,当我们递归左右子树的时候,说明该区间内不同的数字个数一定会减少,而线段树每层节点表示的区间内的不同数字个数一共是 的,一共有 层,因此递归的总时间复杂度是 。再加上每次操作的复杂度,总时间复杂度为
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
typedef pair<int,int> pii;
#define _ 1000006
#define LOCAL
namespace ly
{
namespace IO
{
#ifndef LOCAL
constexpr auto maxn=1<<20;
char in[maxn],out[maxn],*p1=in,*p2=in,*p3=out;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,maxn,stdin),p1==p2)?EOF:*p1++)
#define flush() (fwrite(out,1,p3-out,stdout))
#define putchar(x) (p3==out+maxn&&(flush(),p3=out),*p3++=(x))
class Flush{public:~Flush(){flush();}}_;
#endif
namespace usr
{
template<typename type>
inline type read(type &x)
{
x=0;bool flag(0);char ch=getchar();
while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return flag?x=-x:x;
}
template<typename type>
inline void write(type x)
{
x<0?x=-x,putchar('-'):0;
static short Stack[50],top(0);
do Stack[++top]=x%10,x/=10;while(x);
while(top) putchar(Stack[top--]|48);
}
inline char read(char &x){do x=getchar();while(isspace(x));return x;}
inline char write(const char &x){return putchar(x);}
inline void read(char *x){static char ch;read(ch);do *(x++)=ch;while(!isspace(ch=getchar())&&~ch);}
template<typename type>inline void write(type *x){while(*x)putchar(*(x++));}
inline void read(string &x){static char ch;read(ch),x.clear();do x+=ch;while(!isspace(ch=getchar())&&~ch);}
inline void write(const string &x){for(int i=0,len=x.length();i<len;++i)putchar(x[i]);}
template<typename type,typename...T>inline void read(type &x,T&...y){read(x),read(y...);}
template<typename type,typename...T>
inline void write(const type &x,const T&...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}
template<typename type>
inline void put(const type &x,bool flag=1){write(x),flag?putchar('\n'):putchar(' ');}
}
#ifndef LOCAL
#undef getchar
#undef flush
#undef putchar
#endif
}using namespace IO::usr;
}using namespace ly::IO::usr;
struct Node{int sum,mx,se,tag,cnt;} tree[_<<2];
int in[_];
inline void pushup(int k){
nt.sum=lt.sum+rt.sum;
nt.mx=max(lt.mx,rt.mx);
if(lt.mx==rt.mx) nt.se=max(lt.se,rt.se),nt.cnt=lt.cnt+rt.cnt;
else nt.se=max(min(lt.mx,rt.mx),max(lt.se,rt.se)),nt.cnt=lt.mx>rt.mx?lt.cnt:rt.cnt;
}
inline void update(int k,int x){if(x<nt.mx) nt.sum-=(nt.mx-x)*nt.cnt,nt.mx=nt.tag=x;}
inline void pushdown(int k){if(nt.tag!=-1) update(ls(k),nt.tag),update(rs(k),nt.tag),nt.tag=-1;}
void build(int k,int l,int r){
nt.tag=-1;
if(l==r) return nt.sum=nt.mx=in[l],nt.se=-1,nt.cnt=1,void();
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
void modify(int k,int l,int r,int x,int y,int v){
if(v>=nt.mx) return;
if(l>=x&&r<=y&&v>nt.se) return update(k,v),void();
int mid=(l+r)>>1;
pushdown(k);
if(x<=mid) modify(ls(k),l,mid,x,y,v);
if(y>mid) modify(rs(k),mid+1,r,x,y,v);
pushup(k);
}
int query_max(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.mx;
int mid=(l+r)>>1,res=-inf;
pushdown(k);
if(x<=mid) res=max(res,query_max(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query_max(rs(k),mid+1,r,x,y));
return res;
}
int query_sum(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.sum;
int mid=(l+r)>>1,res=0;
pushdown(k);
if(x<=mid) res+=query_sum(ls(k),l,mid,x,y);
if(y>mid) res+=query_sum(rs(k),mid+1,r,x,y);
return res;
}
inline void subtask(){
int n,m;read(n);read(m);
for(int i=1;i<=n;i++) read(in[i]);
build(1,1,n);
while(m--){
int op,l,r,x;read(op),read(l),read(r);
if(op==0) read(x),modify(1,1,n,l,r,x);
if(op==1) put(query_max(1,1,n,l,r));
if(op==2) put(query_sum(1,1,n,l,r));
}
}
signed main(){
//ios::sync_with_stdio(false);
int t;read(t);
while(t--) subtask();
return 0;
}
现在既有加减的懒标记,又有取 min 的懒标记,难点在于如何 pushdown 这两个标记
由于取 min 需要依赖原有的值,所以加减的懒标记应该先于 min 懒标记下放
对于加减 ,下放的时候直接让子节点的加减懒标记和取 min 懒标记也加减 就可以了
对于对 取 min,下放的时候直接将子节点的取 min 懒标记置为 。注意这么做的前提是,区间内所有数的最大值是大于 的,这样的话可以保证如果多次下放取 min 懒标记,懒标记的值是单调的,直接置为 是没问题的
但是有了这个操作之后,上述的时间复杂度分析的方法不适用了。论文[3]中给出了另一种分析思路,分析出来的时间复杂度为
P10639 BZOJ4695 最佳女选手
https://www.luogu.com.cn/problem/P10639
题目描述
给定一个长度为 的序列,要求支持以下 种操作:
数据范围
数据保证,,。
当进行 操作时,;
当进行 操作时,。
笔记
现在在支持区间加减的基础上,既支持取 min 又支持取 max。我们可以维护区间最大值,区间次大值,区间最大值个数,区间最小值,区间次小值,区间最小值个数,区间和,以及取 min 懒标记,取 max 懒标记,加减懒标记
有三个懒标记,如何下放就成了问题,显然我们还是先下放加减懒标记
后面先下放取 min 懒标记还是先下放取 max 懒标记就无所谓了,我们先下放取 min 懒标记
同时,当一个区间内数字较少的时候,可能会出现最大值、最小值、次小值、次大值中间有几个是相同的
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
typedef pair<int,int> pii;
#define _ 500005
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define LOCAL
namespace ly
{
namespace IO
{
#ifndef LOCAL
constexpr auto maxn=1<<20;
char in[maxn],out[maxn],*p1=in,*p2=in,*p3=out;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,maxn,stdin),p1==p2)?EOF:*p1++)
#define flush() (fwrite(out,1,p3-out,stdout))
#define putchar(x) (p3==out+maxn&&(flush(),p3=out),*p3++=(x))
class Flush{public:~Flush(){flush();}}_;
#endif
namespace usr
{
template<typename type>
inline type read(type &x)
{
x=0;bool flag(0);char ch=getchar();
while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return flag?x=-x:x;
}
template<typename type>
inline void write(type x)
{
x<0?x=-x,putchar('-'):0;
static short Stack[50],top(0);
do Stack[++top]=x%10,x/=10;while(x);
while(top) putchar(Stack[top--]|48);
}
inline char read(char &x){do x=getchar();while(isspace(x));return x;}
inline char write(const char &x){return putchar(x);}
inline void read(char *x){static char ch;read(ch);do *(x++)=ch;while(!isspace(ch=getchar())&&~ch);}
template<typename type>inline void write(type *x){while(*x)putchar(*(x++));}
inline void read(string &x){static char ch;read(ch),x.clear();do x+=ch;while(!isspace(ch=getchar())&&~ch);}
inline void write(const string &x){for(int i=0,len=x.length();i<len;++i)putchar(x[i]);}
template<typename type,typename...T>inline void read(type &x,T&...y){read(x),read(y...);}
template<typename type,typename...T>
inline void write(const type &x,const T&...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}
template<typename type>
inline void put(const type &x,bool flag=1){write(x),flag?putchar('\n'):putchar(' ');}
}
#ifndef LOCAL
#undef getchar
#undef flush
#undef putchar
#endif
}using namespace IO::usr;
}using namespace ly::IO::usr;
int in[_];
struct Node{int max1,max2,min1,min2,cmin,cmax,sum,tmin,tmax,tsum;} tree[_<<2];
inline void pushup(int k){
nt.sum=lt.sum+rt.sum;
nt.max1=max(lt.max1,rt.max1);
if(lt.max1==rt.max1) nt.max2=max(lt.max2,rt.max2),nt.cmax=lt.cmax+rt.cmax;
else nt.max2=max(min(lt.max1,rt.max1),max(lt.max2,rt.max2)),nt.cmax=lt.max1>rt.max1?lt.cmax:rt.cmax;
nt.min1=min(lt.min1,rt.min1);
if(lt.min1==rt.min1) nt.min2=min(lt.min2,rt.min2),nt.cmin=lt.cmin+rt.cmin;
else nt.min2=min(max(lt.min1,rt.min1),min(lt.min2,rt.min2)),nt.cmin=lt.min1<rt.min1?lt.cmin:rt.cmin;
}
void build(int k,int l,int r){
nt.tmin=inf,nt.tmax=-inf,nt.tsum=0;
if(l==r) return nt.max1=nt.min1=nt.sum=in[l],nt.max2=-inf,nt.min2=inf,nt.cmax=nt.cmin=1,void();
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
inline void pushsum(int k,int l,int r,int v){
nt.sum+=(r-l+1ll)*v;
nt.max1+=v,nt.min1+=v;
if(nt.max2!=-inf) nt.max2+=v;
if(nt.min2!=inf) nt.min2+=v;
if(nt.tmin!=inf) nt.tmin+=v;
if(nt.tmax!=-inf) nt.tmax+=v;
nt.tsum+=v;
}
inline void pushmin(int k,int v){
if(nt.max1<=v) return;
nt.sum-=(nt.max1-v)*nt.cmax;
if(nt.max1==nt.min2) nt.min2=v;
if(nt.max1==nt.min1) nt.min1=v;
nt.tmax=min(nt.tmax,v),nt.max1=v,nt.tmin=v;
}
inline void pushmax(int k,int v){
if(nt.min1>=v) return;
nt.sum+=(v-nt.min1)*nt.cmin;
if(nt.min1==nt.max2) nt.max2=v;
if(nt.min1==nt.max1) nt.max1=v;
nt.tmin=max(nt.tmin,v),nt.min1=v,nt.tmax=v;
}
inline void pushdown(int k,int l,int r,int mid){
if(nt.tsum) pushsum(ls(k),l,mid,nt.tsum),pushsum(rs(k),mid+1,r,nt.tsum);
if(nt.tmin!=inf) pushmin(ls(k),nt.tmin),pushmin(rs(k),nt.tmin);
if(nt.tmax!=-inf) pushmax(ls(k),nt.tmax),pushmax(rs(k),nt.tmax);
nt.tsum=0,nt.tmax=-inf,nt.tmin=inf;
}
void modify_sum(int k,int l,int r,int x,int y,int v){
if(l>=x&&r<=y) return pushsum(k,l,r,v),void();
int mid=(l+r)>>1;
pushdown(k,l,r,mid);
if(x<=mid) modify_sum(ls(k),l,mid,x,y,v);
if(y>mid) modify_sum(rs(k),mid+1,r,x,y,v);
pushup(k);
}
void modify_min(int k,int l,int r,int x,int y,int v){
if(nt.max1<=v) return;
if(l>=x&&r<=y&&v>nt.max2) return pushmin(k,v),void();
int mid=(l+r)>>1;
pushdown(k,l,r,mid);
if(x<=mid) modify_min(ls(k),l,mid,x,y,v);
if(y>mid) modify_min(rs(k),mid+1,r,x,y,v);
pushup(k);
}
void modify_max(int k,int l,int r,int x,int y,int v){
if(nt.min1>=v) return;
if(l>=x&&r<=y&&v<nt.min2) return pushmax(k,v),void();
int mid=(l+r)>>1;
pushdown(k,l,r,mid);
if(x<=mid) modify_max(ls(k),l,mid,x,y,v);
if(y>mid) modify_max(rs(k),mid+1,r,x,y,v);
pushup(k);
}
int query_sum(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.sum;
int mid=(l+r)>>1,res=0;
pushdown(k,l,r,mid);
if(x<=mid) res+=query_sum(ls(k),l,mid,x,y);
if(y>mid) res+=query_sum(rs(k),mid+1,r,x,y);
return res;
}
int query_min(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.min1;
int mid=(l+r)>>1,res=inf;
pushdown(k,l,r,mid);
if(x<=mid) res=min(res,query_min(ls(k),l,mid,x,y));
if(y>mid) res=min(res,query_min(rs(k),mid+1,r,x,y));
return res;
}
int query_max(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.max1;
int mid=(l+r)>>1,res=-inf;
pushdown(k,l,r,mid);
if(x<=mid) res=max(res,query_max(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query_max(rs(k),mid+1,r,x,y));
return res;
}
inline void subtask(){
int n,m;read(n);
for(int i=1;i<=n;i++) read(in[i]);
build(1,1,n);read(m);
while(m--){
int op,l,r,x;read(op),read(l),read(r);
if(op<=3) read(x);
if(op==1) modify_sum(1,1,n,l,r,x);
if(op==2) modify_max(1,1,n,l,r,x);
if(op==3) modify_min(1,1,n,l,r,x);
if(op==4) put(query_sum(1,1,n,l,r));
if(op==5) put(query_max(1,1,n,l,r));
if(op==6) put(query_min(1,1,n,l,r));
}
}
signed main(){
//ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
对于一些不太复杂的问题,我们可以沿用传统懒标记的做法
P4314 CPU 监控
https://www.luogu.com.cn/problem/P4314
题目描述
个数, 个操作:
数据范围
,数字值域为 int 取值范围
笔记
本题和势能线段树没有什么关系,只是考虑以下如何用传统的懒标记解决区间历史最值问题
第一个难点是,既有区间加和区间推平,懒标记怎么下放。我们不难发现,作用完区间推平之后,后面无论是区间加还是区间推平,都可以归为区间推平(毕竟推平后所有数就都相等了)。所以我们可以考虑先下放区间加标记,再下放区间推平标记。下放区间加标记时,判断是否已经有了区间推平标记,如果有,就直接加到区间推平标记上,如果没有,就加到区间加标记上。下放区间推平标记就正常下放即可
第二个难点是如何处理区间历史最值,首先一个朴素的想法就是多维护一个区间历史最大值 即可,但这样就会出现问题:比如父节点一开始都为 ,然后对 推平,然后对 推平,然后后面操作访问到子节点时,子节点只会接收到对 推平的懒标记,之前对 推平的懒标记就被忽略了,但是子节点的历史最值是
因此我们还需要记录推平标记的历史最值,加减标记的历史最值
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
#define _ 100005
typedef pair<int,int> pii;
struct Node{int mx,hmx,cov,hcov,iscov,add,hadd;} tree[_<<2];
int in[_];
inline void pushup(int k){nt.mx=max(lt.mx,rt.mx),nt.hmx=max(lt.hmx,rt.hmx);}
void build(int k,int l,int r){
nt.iscov=false,nt.add=0;
if(l==r) return nt.mx=nt.hmx=in[l],void();
int mid=(l+r)>>1;
build(ls(k),l,mid);build(rs(k),mid+1,r);
pushup(k);
}
void pushcov(int k,int v,int hv){
if(nt.iscov) nt.hcov=max(nt.hcov,hv);
else nt.iscov=true,nt.hcov=hv;
nt.hmx=max(nt.hmx,hv);
nt.cov=nt.mx=v;
nt.add=0;
}
void pushsum(int k,int v,int hv){
if(nt.iscov) pushcov(k,nt.cov+v,nt.cov+hv);
else{
nt.hadd=max(nt.hadd,nt.add+hv);
nt.hmx=max(nt.hmx,nt.mx+hv);
nt.add+=v,nt.mx+=v;
}
}
void pushdown(int k){
pushsum(ls(k),nt.add,nt.hadd);
pushsum(rs(k),nt.add,nt.hadd);//nt.add可能为0,但是仍下放,因为nt.hadd会有贡献
nt.add=nt.hadd=0;
if(nt.iscov){
pushcov(ls(k),nt.cov,nt.hcov);
pushcov(rs(k),nt.cov,nt.hcov);
nt.iscov=false;
}
}
void modify_sum(int k,int l,int r,int x,int y,int v){
if(l>=x&&r<=y) return pushsum(k,v,v),void();
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid) modify_sum(ls(k),l,mid,x,y,v);
if(y>mid) modify_sum(rs(k),mid+1,r,x,y,v);
pushup(k);
}
void modify_cover(int k,int l,int r,int x,int y,int v){
if(l>=x&&r<=y) return pushcov(k,v,v),void();
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid) modify_cover(ls(k),l,mid,x,y,v);
if(y>mid) modify_cover(rs(k),mid+1,r,x,y,v);
pushup(k);
}
int query_max(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.mx;
pushdown(k);
int mid=(l+r)>>1,res=-inf;
if(x<=mid) res=max(res,query_max(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query_max(rs(k),mid+1,r,x,y));
return res;
}
int query_hmax(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.hmx;
pushdown(k);
int mid=(l+r)>>1,res=-inf;
if(x<=mid) res=max(res,query_hmax(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query_hmax(rs(k),mid+1,r,x,y));
return res;
}
inline void subtask(){
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>in[i];
build(1,1,n);
int m;cin>>m;
while(m--){
char op;int l,r,x;cin>>op>>l>>r;
if(op=='Q') cout<<query_max(1,1,n,l,r)<<endl;
if(op=='A') cout<<query_hmax(1,1,n,l,r)<<endl;
if(op=='P') cin>>x,modify_sum(1,1,n,l,r,x);
if(op=='C') cin>>x,modify_cover(1,1,n,l,r,x);
}
}
signed main(){
ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
同向指的是,比如,我维护区间最小值,操作也是区间取 min,这种情况下可以继续沿用上述传统的懒标记方法
#164. 【清华集训2015】V
https://vjudge.net/problem/UniversalOJ-164
题目描述
给出一个长度为 的序列, 次操作:
数字值域最大为
笔记
为了简单起见,我们把区间推平操作给转化为先减去正无穷,然后对 取 max。不然再搞一个区间推平的懒标记就有点复杂了
于是本题和上题的区别就是多了个区间取 max,我们多维护两个懒标记:取 max 懒标记,历史最大取 max 懒标记
取 max 标记和加减标记下放时如何处理在前文已经说了
值得注意的是本题涉及到减去正无穷,所以有两个注意点:首先这个无穷值不能太小,不然我加了一堆 ,然后再减去无穷值之后得到的结果还是正数;这个无穷值不能太大,不然我减两次无穷就炸了,代码中的解决方案是判断当前值是否小于了 -inf
,小于的话就不再继续减了
cpp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=5e14;
const int mod=0;
typedef pair<int,int> pii;
#define _ 500005
int in[_],n,m;
struct Node{int mx,hmx,add,hadd,tmx,htmx;} tree[_<<2];
inline void pushup(int k){nt.mx=max(lt.mx,rt.mx),nt.hmx=max(lt.hmx,rt.hmx);}
void build(int k,int l,int r){
nt.tmx=nt.htmx=-inf;
if(l==r) return nt.mx=nt.hmx=in[l],void();
int mid=(l+r)>>1;
build(ls(k),l,mid);build(rs(k),mid+1,r);
pushup(k);
}
inline void pushadd(int k,int v,int hv){
nt.hadd=max(nt.hadd,nt.add+hv);
nt.hmx=max(nt.hmx,nt.mx+hv);
if(nt.tmx!=-inf) nt.htmx=max(nt.htmx,nt.tmx+hv),nt.tmx+=nt.tmx<-inf?0:v;
nt.add+=nt.add<-inf?0:v,nt.mx+=nt.mx<-inf?0:v;
}
inline void pushmax(int k,int v,int hv){
nt.htmx=max(nt.htmx,hv);
nt.hmx=max(nt.hmx,max(nt.mx,hv));
nt.tmx=max(nt.tmx,v),nt.mx=max(nt.mx,v);
}
inline void pushdown(int k){
pushadd(ls(k),nt.add,nt.hadd);
pushadd(rs(k),nt.add,nt.hadd);
nt.add=nt.hadd=0;
if(nt.tmx!=-inf){
pushmax(ls(k),nt.tmx,nt.htmx);
pushmax(rs(k),nt.tmx,nt.htmx);
nt.tmx=nt.htmx=-inf;
}
}
void modify_add(int k,int l,int r,int x,int y,int v){
if(l>=x&&r<=y) return pushadd(k,v,v),void();
int mid=(l+r)>>1;
pushdown(k);
if(x<=mid) modify_add(ls(k),l,mid,x,y,v);
if(y>mid) modify_add(rs(k),mid+1,r,x,y,v);
pushup(k);
}
void modify_max(int k,int l,int r,int x,int y,int v){
if(l>=x&&r<=y) return pushmax(k,v,v),void();
int mid=(l+r)>>1;
pushdown(k);
if(x<=mid) modify_max(ls(k),l,mid,x,y,v);
if(y>mid) modify_max(rs(k),mid+1,r,x,y,v);
pushup(k);
}
int query_p(int k,int l,int r,int x){
if(l==r) return nt.mx;
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid) return query_p(ls(k),l,mid,x);
else return query_p(rs(k),mid+1,r,x);
}
int query_h(int k,int l,int r,int x){
if(l==r) return nt.hmx;
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid) return query_h(ls(k),l,mid,x);
else return query_h(rs(k),mid+1,r,x);
}
inline void subtask(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>in[i];
build(1,1,n);
while(m--){
int op,l,r,x;cin>>op>>l;
if(op==1) cin>>r>>x,modify_add(1,1,n,l,r,x);
if(op==2) cin>>r>>x,modify_add(1,1,n,l,r,-x),modify_max(1,1,n,l,r,0);
if(op==3) cin>>r>>x,modify_add(1,1,n,l,r,-inf),modify_max(1,1,n,l,r,x);
if(op==4) cout<<query_p(1,1,n,l)<<endl;
if(op==5) cout<<query_h(1,1,n,l)<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
比如,我维护区间最小值,但操作是区间取 max,这时候就需要使用势能线段树了
P6242 【模板】线段树 3(区间最值操作、区间历史最值)
https://www.luogu.com.cn/problem/P6242
题目描述
给出一个长度为 的数列 ,同时定义一个辅助数组 , 开始与 完全相同。接下来进行了 次操作,操作有五种类型,按以下格式给出:
1 l r k
:对于所有的 ,将 加上 ( 可以为负数)。2 l r v
:对于所有的 ,将 变成 。3 l r
:求 。4 l r
:对于所有的 ,求 的最大值。5 l r
:对于所有的 ,求 的最大值。在每一次操作后,我们都进行一次更新,让 。
数据范围
对于全部测试数据,保证 ,,,,,。
笔记
如果本题没有区间历史最值的话,做法在之前已经说过了。我们回顾一下之前的做法,之前的做法中维护区间次值,实际上是把维护的数分为了两个部分:最值和非最值。如果操作仅对最值产生影响,那么可以快速处理。如果操作影响到了非最值,那么就暴力处理,同时这个划分方法确保了暴力处理是可以让势能下降的,从而确保了时间复杂度。
并且我们还会发现,划分数域后,所有的操作都可以视为对最值进行直接的修改,即使在某个节点上是对非最值修改,那么递归下去,也是对某个子节点的最值进行修改。这样一来,我们可以把所有的取最值操作转化为对最值的加减操作。比如当前最大值为 ,我想对 取 min,那么我们可以直接让最大值减去 ,在下传标记的时候,我们判断一下左右子节点的最大值是否是父节点的最大值,是的话就把这个仅对最大值操作的标记传过去。这个转化可以让我们抛弃掉不太好处理的区间最值标记,转向更加处理的加减标记
现在这道题,如果我们仍然沿用传统的懒标记方法就不太好处理了。但这种反向的特性,给了我们划分数域处理的机会。我们把区间内的数字划分为最大值和非最大值两部分,每部分维护加减懒标记和历史最大加减懒标记。
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
typedef pair<int,int> pii;
#define _ 500005
struct Tag{int add1,hadd1,add2,hadd2;};//1结尾为作用于最大,2结尾为作用于次大
struct Node{int mx,hmx,se,sum,cnt1;Tag tag;} tree[_<<2];
int in[_],n,m;
inline void pushup(int k){
nt.sum=lt.sum+rt.sum;
nt.mx=max(lt.mx,rt.mx);
nt.hmx=max(lt.hmx,rt.hmx);
if(lt.mx==rt.mx) nt.cnt1=lt.cnt1+rt.cnt1,nt.se=max(lt.se,rt.se);
else nt.se=max(min(lt.mx,rt.mx),max(lt.se,rt.se)),nt.cnt1=lt.mx>rt.mx?lt.cnt1:rt.cnt1;
}
void build(int k,int l,int r){
if(l==r) return nt.mx=nt.hmx=nt.sum=in[l],nt.cnt1=1,nt.se=-inf,void();
int mid=(l+r)>>1;
build(ls(k),l,mid);build(rs(k),mid+1,r);
pushup(k);
}
inline void update(int k,int l,int r,Tag tag){
nt.sum+=tag.add1*nt.cnt1+tag.add2*(r-l+1-nt.cnt1);
nt.tag.hadd1=max(nt.tag.hadd1,nt.tag.add1+tag.hadd1);
nt.tag.hadd2=max(nt.tag.hadd2,nt.tag.add2+tag.hadd2);
nt.tag.add1+=tag.add1;
nt.tag.add2+=tag.add2;
nt.hmx=max(nt.hmx,nt.mx+tag.hadd1);
nt.mx+=tag.add1;
if(nt.se!=-inf) nt.se+=tag.add2;
}
inline void pushdown(int k,int l,int r){
int mx=max(lt.mx,rt.mx),mid=(l+r)>>1;
Tag tag1=nt.tag,tag2=nt.tag;
tag2.add1=nt.tag.add2,tag2.hadd1=nt.tag.hadd2;//构造区间内没有父节点最大值时,需要作用的标记。此时子节点的最大值相当于父节点的非最大值
if(mx==lt.mx) update(ls(k),l,mid,tag1);
else update(ls(k),l,mid,tag2);
if(mx==rt.mx) update(rs(k),mid+1,r,tag1);
else update(rs(k),mid+1,r,tag2);
nt.tag.add1=nt.tag.add2=nt.tag.hadd1=nt.tag.hadd2=0;
}
void modify_add(int k,int l,int r,int x,int y,int v){
if(l>=x&&r<=y){
Tag tag;tag.add2=tag.hadd2=tag.add1=tag.hadd1=v;//无论是最大值还是非最大值,都需要+v
update(k,l,r,tag);
return;
}
int mid=(l+r)>>1;
pushdown(k,l,r);
if(x<=mid) modify_add(ls(k),l,mid,x,y,v);
if(y>mid) modify_add(rs(k),mid+1,r,x,y,v);
pushup(k);
}
void modify_min(int k,int l,int r,int x,int y,int v){
if(v>=nt.mx) return;
if(l>=x&&r<=y&&v>nt.se){
Tag tag;tag.add1=tag.hadd1=v-nt.mx,tag.add2=tag.hadd2=0;//仅最大值作用
update(k,l,r,tag);
return;
}
int mid=(l+r)>>1;
pushdown(k,l,r);
if(x<=mid) modify_min(ls(k),l,mid,x,y,v);
if(y>mid) modify_min(rs(k),mid+1,r,x,y,v);
pushup(k);
}
int query_sum(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.sum;
int mid=(l+r)>>1,res=0;
pushdown(k,l,r);
if(x<=mid) res+=query_sum(ls(k),l,mid,x,y);
if(y>mid) res+=query_sum(rs(k),mid+1,r,x,y);
return res;
}
int query_max(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.mx;
int mid=(l+r)>>1,res=-inf;
pushdown(k,l,r);
if(x<=mid) res=max(res,query_max(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query_max(rs(k),mid+1,r,x,y));
return res;
}
int query_hmax(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.hmx;
int mid=(l+r)>>1,res=-inf;
pushdown(k,l,r);
if(x<=mid) res=max(res,query_hmax(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query_hmax(rs(k),mid+1,r,x,y));
return res;
}
inline void subtask(){
cin>>n>>m;for(int i=1;i<=n;i++) cin>>in[i];
build(1,1,n);
while(m--){
int op,l,r,x;cin>>op>>l>>r;
if(op==1) cin>>x,modify_add(1,1,n,l,r,x);
if(op==2) cin>>x,modify_min(1,1,n,l,r,x);
if(op==3) cout<<query_sum(1,1,n,l,r)<<endl;
if(op==4) cout<<query_max(1,1,n,l,r)<<endl;
if(op==5) cout<<query_hmax(1,1,n,l,r)<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
题目描述
给出一个长度为 的序列, 次操作:
笔记
我们要维护的序列为 ,历史最大值为序列
然后我们令 ,容易发现 恒成立
当我们对 加 的时候, 也加了 。此时,如果 仍小于等于 ,说明 并没有更新掉历史最大值。 如果 大于 0 了,说明 比历史最大值还大, 更新为 ,于是 又变为了 0
于是我们可以再维护 ,实现 区间加、区间取 、询问区间和 即可
询问区间历史最大值之和时,
题目描述
给出一个长度为 的序列 和 个操作:
笔记
我们给每个询问和操作分配一个时间,第 个询问或操作的时间是 。将序列的初始状态视为有 个操作一,时间都是
然后我们对于每个位置,求出这个位置上哪些时间段内是哪些数,比如一个位置初始时是 ,在操作 被修改成了 ,那么我们就说这个位置的数在时间段 是 ,在时间段 是
然后我们建立一个线段树,这个线段树的下标表示的是时间,维护在某个时间的后缀最小值为多少
然后我们从序列的最后一个位置开始向前枚举,当我们枚举到一个位置时,这个位置可能在不同时间有不同的数字,因此不同的时间,这个位置开始的后缀最小值也是不一样的。我们可以根据之前求出的哪些时间段内是哪些数,进行区间取 min。同时,可能还会存在关于这个位置的询问,但我们目前做的只能知道这个位置在不同时间的后缀最小值是多少
我们考虑,当我们从序列的最后一个位置开始向前枚举,不断在某个时间点上对一些数字取 的时候,成功取 min (即取 min 后数字变得更小了)的次数即为,在当前时间点上,从当前枚举到的位置作为起点开始的后缀最小值个数。成功取 min 的次数可以在区间取 min 的时候顺手记录
因此,关于这个位置的询问,我们只需要根据这个询问的时间点,在线段树上进行单点询问即可
回顾我们这个离线的过程,从序列的最后一个位置开始向前枚举,可以使得当前的状态仅由后面的位置贡献产生;线段树维护在某个时间的后缀最小值为多少,可以做到快速处理修改操作产生的后续影响
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123#include<bits/stdc++.h>
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
typedef pair<int,int> pii;
#define _ 1000006
#define LOCAL
namespace ly
{
namespace IO
{
#ifndef LOCAL
constexpr auto maxn=1<<20;
char in[maxn],out[maxn],*p1=in,*p2=in,*p3=out;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,maxn,stdin),p1==p2)?EOF:*p1++)
#define flush() (fwrite(out,1,p3-out,stdout))
#define putchar(x) (p3==out+maxn&&(flush(),p3=out),*p3++=(x))
class Flush{public:~Flush(){flush();}}_;
#endif
namespace usr
{
template<typename type>
inline type read(type &x)
{
x=0;bool flag(0);char ch=getchar();
while(!isdigit(ch)) flag^=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return flag?x=-x:x;
}
template<typename type>
inline void write(type x)
{
x<0?x=-x,putchar('-'):0;
static short Stack[50],top(0);
do Stack[++top]=x%10,x/=10;while(x);
while(top) putchar(Stack[top--]|48);
}
inline char read(char &x){do x=getchar();while(isspace(x));return x;}
inline char write(const char &x){return putchar(x);}
inline void read(char *x){static char ch;read(ch);do *(x++)=ch;while(!isspace(ch=getchar())&&~ch);}
template<typename type>inline void write(type *x){while(*x)putchar(*(x++));}
inline void read(string &x){static char ch;read(ch),x.clear();do x+=ch;while(!isspace(ch=getchar())&&~ch);}
inline void write(const string &x){for(int i=0,len=x.length();i<len;++i)putchar(x[i]);}
template<typename type,typename...T>inline void read(type &x,T&...y){read(x),read(y...);}
template<typename type,typename...T>
inline void write(const type &x,const T&...y){write(x),putchar(' '),write(y...),sizeof...(y)^1?0:putchar('\n');}
template<typename type>
inline void put(const type &x,bool flag=1){write(x),flag?putchar('\n'):putchar(' ');}
}
#ifndef LOCAL
#undef getchar
#undef flush
#undef putchar
#endif
}using namespace IO::usr;
}using namespace ly::IO::usr;
struct Node{int mx,se,val,tag;} tree[_<<2];
inline void pushup(int k){
nt.mx=max(lt.mx,rt.mx);
if(lt.mx==rt.mx) nt.se=max(lt.se,rt.se);
else nt.se=max(min(lt.mx,rt.mx),max(lt.se,rt.se));
}
inline void update(int k,int v,int d){if(v<nt.mx) nt.mx=nt.tag=v,nt.val+=d;}
inline void pushdown(int k){if(nt.tag) update(ls(k),nt.tag,nt.val),update(rs(k),nt.tag,nt.val),nt.tag=0,nt.val=0;}
void build(int k,int l,int r){
if(l==r) return nt.mx=inf,nt.se=-1,void();
int mid=(l+r)>>1;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
pushup(k);
}
void modify(int k,int l,int r,int x,int y,int v){
if(nt.mx<=v) return;
if(l>=x&&r<=y&&nt.se<v) return update(k,v,1),void();
int mid=(l+r)>>1;
pushdown(k);
if(x<=mid) modify(ls(k),l,mid,x,y,v);
if(y>mid) modify(rs(k),mid+1,r,x,y,v);
pushup(k);
}
int query(int k,int l,int r,int x){
if(l==r) return nt.val;
int mid=(l+r)>>1;
pushdown(k);
if(x<=mid) return query(ls(k),l,mid,x);
else return query(rs(k),mid+1,r,x);
}
struct O{int x,l,r;};
struct Q{int id,time;};
vector<O> g[_];
vector<Q> q[_];
int cntq,ans[_];
inline void subtask(){
int n,m;read(n),read(m);
for(int i=1,x;i<=n;i++) read(x),g[i].push_back(O{x,1,m+1});
for(int i=1;i<=m;i++){
int op,x,v;read(op),read(x);
if(op==1){
read(v);
(*(--g[x].end())).r=i-1;
g[x].push_back(O{v,i,m+1});
}else q[x].push_back(Q{++cntq,i});
}
build(1,1,m+1);
for(int i=n;i>=1;i--){
for(auto to:g[i]) modify(1,1,m+1,to.l,to.r,to.x);
for(auto to:q[i]) ans[to.id]=query(1,1,m+1,to.time);
}
for(int i=1;i<=cntq;i++) put(ans[i]);
}
signed main(){
//ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
https://www.luogu.com.cn/problem/SP1557
题目描述
给出 个数, 次询问,求最大字段和,相同的数字只算一次,选出的子段可以为空,
笔记
先不考虑相同的数字只算一次如何处理
先把询问离线下来,按询问区间右端点从小到大排序。然后枚举询问,不断从左到右添加数字到线段树中,直到添加的位置到达了询问的右端点。对于第 个数字 ,我们让线段树上区间 全加上 ,这样线段树上维护的就是从当前询问的右端点开始,往前的前缀和
但是我们选出的子段不一定要包含当前询问的右端点。比如最大子段是区间 ,当前询问的右端点为 ,那么 到 之间的数字是不应该产生贡献的,也就是之前线段树 位置的值很大,但随着 到 之间的数字加入, 位置的值变小了。我们发现为了得到 位置的最大值,可以考虑维护区间的历史最大值
现在考虑相同的数字只算一次,我们只需要记录当前数字 上一次的出现位置 ,然后让区间 加上 就可以了
cpp1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls(k) (k)<<1
#define rs(k) (k)<<1|1
#define nt tree[k]
#define lt tree[ls(k)]
#define rt tree[rs(k)]
#define debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=0;
typedef pair<int,int> pii;
#define _ 100005
const int off=100000;
struct Node{int add,hadd,mx,hmx;} tree[_<<2];
int n,m,in[_],las[_<<3];
inline void pushup(int k){nt.mx=max(lt.mx,rt.mx),nt.hmx=max(lt.hmx,rt.hmx);}
inline void pushadd(int k,int v,int hv){
nt.hadd=max(nt.hadd,nt.add+hv);
nt.hmx=max(nt.hmx,nt.mx+hv);
nt.add+=v,nt.mx+=v;
}
inline void pushdown(int k){pushadd(ls(k),nt.add,nt.hadd);pushadd(rs(k),nt.add,nt.hadd);nt.add=nt.hadd=0;}
void modify(int k,int l,int r,int x,int y,int v){
if(l>=x&&r<=y) return pushadd(k,v,v),void();
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid) modify(ls(k),l,mid,x,y,v);
if(y>mid) modify(rs(k),mid+1,r,x,y,v);
pushup(k);
}
int query(int k,int l,int r,int x,int y){
if(l>=x&&r<=y) return nt.hmx;
pushdown(k);
int mid=(l+r)>>1,res=-inf;
if(x<=mid) res=max(res,query(ls(k),l,mid,x,y));
if(y>mid) res=max(res,query(rs(k),mid+1,r,x,y));
return res;
}
struct Q{int index,l,r,ans;} q[_];
inline void subtask(){
cin>>n;for(int i=1;i<=n;i++) cin>>in[i];cin>>m;
for(int i=1;i<=m;i++) cin>>q[i].l>>q[i].r,q[i].index=i;
sort(q+1,q+m+1,[](Q x,Q y){return x.r<y.r;});
for(int i=1,p=0;i<=m;i++){
while(p<q[i].r){
p++;
modify(1,1,n,las[off+in[p]]+1,p,in[p]);
las[off+in[p]]=p;
}
q[i].ans=query(1,1,n,q[i].l,q[i].r);
}
sort(q+1,q+m+1,[](Q x,Q y){return x.index<y.index;});
for(int i=1;i<=m;i++) cout<<q[i].ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
int t=1;//cin>>t;
while(t--) subtask();
return 0;
}
洛谷-灵梦 《区间最值操作与区间历史最值详解》 ↩︎
吉如一《区间最值操作与历史最值问题》 见 2016年国家集训队论文集 ↩︎