区间最值操作线段树(吉司机线段树,Segment Tree Beats)
【模板】线段树 3(区间最值操作、区间历史最值)
所谓区间最值操作就是将一个区间中的所有值全部与一个给定的值取 \(\min\)。
区间历史最值就是字面意思,在之前任意时刻区间中的最大值。
这个题就是维护 5 种操作。
区间加、区间最小值操作、求区间和、求区间最大值、求区间历史最大值。
考虑区间最小值操作怎么做。发现有这个操作以后不好维护的操作只有区间最大值和区间和。考虑用什么来维护。
我们维护一个区间的最大值 \(mf\) 和严格次小值 \(ms\)(分别是 \(maxfirst\) 和 \(maxsecond\) 的缩写),以及区间中最大值的个数 \(cnt\)。我们设维护的区间和是 \(s\)
假设区间最小值操作给定的值是 \(w\),那么在区间上如果 \(mf<w<ms\),那么修改的值的个数和值都确定了,直接根据我们记录的 \(cnt\) 就可以维护 \(s\)。
根据吉司机的复杂度均摊证明这样操作的复杂度上界是严格 \(O(n\log n)\) 的。
于是我们可能会对一个节点的所有最大值单独修改,于是我们要维护一些区间加的标记:
\(t1\) 最大值的区间加标记。
\(t2\) 当前标记下放前的最大值的区间加标记的历史最大值。
\(t3\) 除了最大值以外的区间加标记(因为我们要么所有值都改,要么只修改最大值,所以除了最大值以外的所有值都可以共用一个加法标记)。
\(t4\) 是当前标记下放前的最大值以外的区间加标记的历史最大值。
然后通过维护这 4 个标记就可以完成对节点区间和与区间最大值的维护。
具体的,我们可以实现出最关键的 push_down 下放标记的部分。
void clear(int u){t1[u]=t2[u]=t3[u]=t4[u]=0;}
void update(int u,int l1,int l2,int l3,int l4,int l,int r){
s[u]+=cnt[u]*l1+(r-l+1-cnt[u])*l3;
mh[u]=max(mh[u],mf[u]+l2),mf[u]+=l1;//注意这里加的顺序
if(ms[u]!=-inf) ms[u]+=l3;
t2[u]=max(t2[u],t1[u]+l2),t1[u]+=l1;
t4[u]=max(t4[u],t3[u]+l4),t3[u]+=l3;
}
void push_down(int u,int l,int r){
int maxn=max(mf[ls],mf[rs]);
if(mf[ls]==maxn)update(ls,t1[u],t2[u],t3[u],t4[u],l,mid);else update(ls,t3[u],t4[u],t3[u],t4[u],l,mid);//注意到这里update的范围是子树的范围而不是[l,r]
if(mf[rs]==maxn)update(rs,t1[u],t2[u],t3[u],t4[u],mid+1,r);else update(rs,t3[u],t4[u],t3[u],t4[u],mid+1,r);
clear(u);
}
个人感觉是比较直观的。有一点细节是对于 \(t2\) 与 \(t4\) 的更新上。
注意我们对 \(t2\) 与 \(t4\) 的定义是当前点的标记下放前的历史最大值,因此应该与当前的标记加上从父亲下放下来的标记值取 \(\max\)。
还需要注意的是 push_down 中不能直接比较左右儿子的最大值大小而应该和更新前的最大值作比较,因为更新的时候左右儿子的最大值可能有更改。
然后就是更新节点的 push_up 操作。也相当直观。
void push_up(int u){
mf[u]=max(mf[ls],mf[rs]);mh[u]=max(mh[ls],mh[rs]);
s[u]=s[ls]+s[rs];
if(mf[ls]==mf[rs])ms[u]=max(ms[ls],ms[rs]),cnt[u]=cnt[ls]+cnt[rs];
if(mf[ls]<mf[rs])ms[u]=max(mf[ls],ms[rs]),cnt[u]=cnt[rs];
if(mf[ls]>mf[rs])ms[u]=max(ms[ls],mf[rs]),cnt[u]=cnt[ls];
}
直接维护即可。
然后就是正常的建树和各种操作。比较平凡就不多说。注意在 modify 里面直接用 \(update\) 来更新,方便快捷。
还有 操作 4、5 我合并在一起写了,两个差不多。
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+7,inf=2e17+7;
int a[N];
namespace Gsgt{
int mf[N<<2],ms[N<<2],mh[N<<2],s[N<<2],cnt[N<<2];//最大值,次大值,历史最大值,区间和,最大值个数
int t1[N<<2],t2[N<<2],t3[N<<2],t4[N<<2];//1 最大值 2 历史最大值 3 次大值(非最大值) 4 历史次大值
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
void clear(int u){t1[u]=t2[u]=t3[u]=t4[u]=0;}
void push_up(int u){
mf[u]=max(mf[ls],mf[rs]);mh[u]=max(mh[ls],mh[rs]);
s[u]=s[ls]+s[rs];
if(mf[ls]==mf[rs])ms[u]=max(ms[ls],ms[rs]),cnt[u]=cnt[ls]+cnt[rs];
if(mf[ls]<mf[rs])ms[u]=max(mf[ls],ms[rs]),cnt[u]=cnt[rs];
if(mf[ls]>mf[rs])ms[u]=max(ms[ls],mf[rs]),cnt[u]=cnt[ls];
}
void update(int u,int l1,int l2,int l3,int l4,int l,int r){
s[u]+=cnt[u]*l1+(r-l+1-cnt[u])*l3;
mh[u]=max(mh[u],mf[u]+l2),mf[u]+=l1;//注意这里加的顺序
if(ms[u]!=-inf) ms[u]+=l3;
t2[u]=max(t2[u],t1[u]+l2),t1[u]+=l1;
t4[u]=max(t4[u],t3[u]+l4),t3[u]+=l3;
}
void push_down(int u,int l,int r){
int maxn=max(mf[ls],mf[rs]);
if(mf[ls]==maxn)update(ls,t1[u],t2[u],t3[u],t4[u],l,mid);else update(ls,t3[u],t4[u],t3[u],t4[u],l,mid);//注意到这里update的范围是子树的范围而不是[l,r]
if(mf[rs]==maxn)update(rs,t1[u],t2[u],t3[u],t4[u],mid+1,r);else update(rs,t3[u],t4[u],t3[u],t4[u],mid+1,r);
clear(u);
}
void build(int u,int l,int r){
if(l==r){mf[u]=mh[u]=s[u]=a[l],ms[u]=-inf,cnt[u]=1;return;}
build(ls,l,mid),build(rs,mid+1,r);push_up(u);
}
void modify1(int u,int l,int r,int ql,int qr,int w){
if(ql<=l&&r<=qr){update(u,w,w,w,w,l,r);return;}
push_down(u,l,r);if(ql<=mid)modify1(ls,l,mid,ql,qr,w);if(qr>mid)modify1(rs,mid+1,r,ql,qr,w);
push_up(u);
}
void modify2(int u,int l,int r,int ql,int qr,int w){
if(mf[u]<=w||ql>r||qr<l)return;
if(ql<=l&&r<=qr&&ms[u]<w){update(u,w-mf[u],w-mf[u],0ll,0ll,l,r);return;}
push_down(u,l,r);modify2(ls,l,mid,ql,qr,w);modify2(rs,mid+1,r,ql,qr,w);push_up(u);
}
int query1(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return s[u];}
int ans=0;push_down(u,l,r);if(ql<=mid)ans+=query1(ls,l,mid,ql,qr);if(qr>mid)ans+=query1(rs,mid+1,r,ql,qr);
return ans;
}
int query2(int u,int l,int r,int ql,int qr,int op){
if(ql<=l&&r<=qr){return (op==4?mf[u]:mh[u]);}
int ans=-inf;push_down(u,l,r);
if(ql<=mid)ans=max(ans,query2(ls,l,mid,ql,qr,op));if(qr>mid)ans=max(ans,query2(rs,mid+1,r,ql,qr,op));
return ans;
}
}
using namespace Gsgt;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m;cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n);
for(int i=1,op,l,r,k;i<=m;i++){
cin>>op>>l>>r;
if(op==1)cin>>k,modify1(1,1,n,l,r,k);
if(op==2)cin>>k,modify2(1,1,n,l,r,k);
if(op==3)cout<<query1(1,1,n,l,r)<<'\n';
if(op==4||op==5)cout<<query2(1,1,n,l,r,op)<<'\n';
}
return 0;
}
P9631 [ICPC 2020 Nanjing R] Just Another Game of Stones
好题难得。最喜欢这种小清新数据结构题了。
区间取 \(\max\) 显然吉司机线段树。考虑题目要求的就是一步可以达到先手必败的方案数,也就是一步可以使异或和恰好为 0 的方案数。
考虑本身的异或和 \(s\),如果我们要使得 \(s\) 可以一步到达 0,设 \(w\) 表示对于位置 \(a_i\) 需要拿恰好 \(w\) 个石子使得 \(s\) 为 0,那么有
条件等价于
显然有 \(w\le a_i\),于是我们只需要使 \(a_i-(a_i\oplus s)\ge 0\) 那 \(i\) 这个石子堆就可以有 1 的贡献。发现式子满足的充要条件是 \(a_i\) 在 \(s\) 的最高的那位为 1。必要性证明显然,充分性考虑反证。
于是可以简单维护,线段树细节不赘述。
code
注意还要考虑 \(x\) 这一堆的贡献。
点击查看代码
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll long long
//namespace FIO{
// template<typename P>
// inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
// template<typename Ty,typename ...Args>
// inline void read(Ty &x,Args &...args) {read(x);read(args...);}
// inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
//}
//using FIO::read;using FIO::write;
const int N=2e5+7,inf=1.5e9+7;
int n,m,a[N];
namespace sgt_beats{ //维护异或和,每个二进制位为 1 的数的个数,两个查询分别为区间异或和和某一位上二进制位为 1 的数的个数(记得加上单独给出的 x)
int s[N<<2],mf[N<<2],ms[N<<2],cnt[N<<2],tag[N<<2],num[N<<2][30]; //最小值,次小值
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
void push_up(int u){
s[u]=s[ls]^s[rs];
if(mf[ls]<mf[rs]){mf[u]=mf[ls],cnt[u]=cnt[ls];ms[u]=min(ms[ls],mf[rs]);}
else if(mf[ls]>mf[rs]){mf[u]=mf[rs],cnt[u]=cnt[rs];ms[u]=min(ms[rs],mf[ls]);}
else {mf[u]=mf[ls];cnt[u]=cnt[ls]+cnt[rs];ms[u]=min(ms[ls],ms[rs]);}
for(int i=0;i<30;i++)num[u][i]=num[ls][i]+num[rs][i];
}
void upd(int u,int w){if(mf[u]>=w)return;for(int i=0;i<30;i++)num[u][i]+=(((w>>i)&1)-((mf[u]>>i)&1))*cnt[u];s[u]^=(cnt[u]&1)*(mf[u]^w);mf[u]=tag[u]=w;} //下传标记的时候可能将标记传到比最小值小的地方,要判断一下
void push_down(int u){if(tag[u]){upd(ls,tag[u]);upd(rs,tag[u]);tag[u]=0;}}
void build(int u,int l,int r){ //tag=0 和没有没区别
if(l==r){s[u]=mf[u]=a[l],ms[u]=inf;cnt[u]=1;for(int i=0;i<30;i++)num[u][i]=((a[l]>>i)&1);return;}
build(ls,l,mid),build(rs,mid+1,r);push_up(u);
}
void modify(int u,int l,int r,int ql,int qr,int w){
if(w<=mf[u])return;
if(ql<=l&&r<=qr&&w<ms[u]){upd(u,w);return;}
push_down(u);
if(ql<=mid)modify(ls,l,mid,ql,qr,w);
if(qr>mid)modify(rs,mid+1,r,ql,qr,w);
push_up(u);
}
int query1(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return s[u];}
push_down(u);int res=0;
if(ql<=mid)res^=query1(ls,l,mid,ql,qr);
if(qr>mid)res^=query1(rs,mid+1,r,ql,qr);
return res;
}
int query2(int u,int l,int r,int ql,int qr,int k){
if(ql<=l&&r<=qr){return num[u][k];}
push_down(u);int res=0;
if(ql<=mid)res+=query2(ls,l,mid,ql,qr,k);
if(qr>mid)res+=query2(rs,mid+1,r,ql,qr,k);
return res;
}
}
bool Med;
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];
sgt_beats::build(1,1,n);
while(m--){
int op,l,r,x;cin>>op>>l>>r>>x;
if(op==1){sgt_beats::modify(1,1,n,l,r,x);}
else{
int s=sgt_beats::query1(1,1,n,l,r),k=0;s^=x;
if(s==0){cout<<"0\n";continue;}
for(int i=0;i<30;i++)if((s>>i)&1)k=i;
cout<<(sgt_beats::query2(1,1,n,l,r,k)+((x>>k)&1))<<'\n';
}
}
cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
return 0;
}

浙公网安备 33010602011771号