Segment tree(线段树)
1.线段树的结构和思想
线段树基本结构
简单操作
1.单点修改:时间复杂度O(log n),因为线段树高是O(log n)的,然后会修改这个点到根的路径上的点权,所以是O(log n)的。
2.区间查询(比如:最小值)
实现
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; int n,q; int a[N]; struct node{ int minv; }seg[N*4]; void update(int id) { seg[id].minv = min(seg[id*2].minv,seg[id*2+1].minv); } void build(int id,int l,int r) { if(l==r) seg[id].minv = a[l]; else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void change(int id,int l,int r,int pos,int val){ if(l==r) { seg[id].minv = val; //a[pos] = val;已经把a数组记到线段树里面了,之后不用了,可以不写 } else { int mid = (l+r)/2; if(pos<=mid)change(id*2,l,mid,pos,val); else change(id*2+1,mid+1,r,pos,val); //重要!改完之后记得更新节点的信息 update(id); } } //O(logn) int query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].minv; int mid = (l+r)/2; if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return min(query(id*2,l,mid,x,mid),query(id*2+1,mid+1,r,mid+1,y)); } } int main() { cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int x,d; cin>>x>>d; change(1,1,n,x,d); } else { int l,r; cin>>l>>r; cout<<query(1,1,n,l,r)<<endl; } } return 0; }
2.单点修改,区间查询(eg1)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; int n,q; int a[N]; struct info { int minv,mincnt; }; info operator+(const info &l,const info &r) { info a; a.minv = min(l.minv,r.minv); if(l.minv==r.minv)a.mincnt = l.mincnt + r.mincnt; else if(l.minv<r.minv)a.mincnt = l.mincnt; else a.mincnt = r.mincnt; return a; } struct node{ info val; }seg[N*4]; void update(int id) { seg[id].val = seg[id*2].val+seg[id*2+1].val; } void build(int id,int l,int r) { if(l==r) seg[id].val = {a[l],1}; else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void change(int id,int l,int r,int pos,int val){ if(l==r) { seg[id].val = {val,1}; } else { int mid = (l+r)/2; if(pos<=mid)change(id*2,l,mid,pos,val); else change(id*2+1,mid+1,r,pos,val); //重要!改完之后记得更新节点的信息 update(id); } } //O(logn) info query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].val; int mid = (l+r)/2; if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y); } } int main() { cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int x,d; cin>>x>>d; change(1,1,n,x,d); } else { int l,r; cin>>l>>r; auto ans = query(1,1,n,l,r); cout<<ans.minv<<" "<<ans.mincnt<<endl; } } return 0; }
2.1.【复杂的信息合并】最大子段和(eg2)
//最大字段和 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; int n,q; int a[N]; struct info { ll mss,mpre,msuf,s; int minv,mincnt; info(){}; info(int a):mss(a),mpre(a),msuf(a),s(a){}; }; info operator+(const info &l,const info &r) { info a; a.mss = max({l.mss,r.mss,l.msuf+r.mpre}); a.mpre = max(l.mpre,l.s+r.mpre); a.msuf = max(r.msuf,r.s+l.msuf); a.s = l.s+r.s; a.minv = min(l.minv,r.minv); return a; } struct node{ info val; }seg[N*4]; void update(int id) { seg[id].val = seg[id*2].val+seg[id*2+1].val; } void build(int id,int l,int r) { if(l==r) seg[id].val = info(a[l]); else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void change(int id,int l,int r,int pos,int val){ if(l==r) { seg[id].val = info(val); } else { int mid = (l+r)/2; if(pos<=mid)change(id*2,l,mid,pos,val); else change(id*2+1,mid+1,r,pos,val); //重要!改完之后记得更新节点的信息 update(id); } } //O(logn) info query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].val; int mid = (l+r)/2; if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y); } } int main() { cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int x,d; cin>>x>>d; change(1,1,n,x,d); } else { int l,r; cin>>l>>r; auto ans = query(1,1,n,l,r); cout<<ans.mss<<endl; } } return 0; }
最大子段和变式
SP2916 GSS5 - Can you answer these queries V
题意:
给定一个序列。查询左端点在
思路:分类讨论。
第一种情况:没有两个区间没有相交部分。
第二种情况:有相交部分。
再分4种:
- ①+②+③
- ①+②
- ②
- ②+③
//最大字段和 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; int n,q; int a[N]; struct info { ll mss,mpre,msuf,s; int minv,mincnt; info(){}; info(int a):mss(a),mpre(a),msuf(a),s(a){}; }; info operator+(const info &l,const info &r) { info a; a.mss = max({l.mss,r.mss,l.msuf+r.mpre}); a.mpre = max(l.mpre,l.s+r.mpre); a.msuf = max(r.msuf,r.s+l.msuf); a.s = l.s+r.s; a.minv = min(l.minv,r.minv); return a; } struct node{ info val; }seg[N*4]; void update(int id) { seg[id].val = seg[id*2].val+seg[id*2+1].val; } void build(int id,int l,int r) { if(l==r) seg[id].val = info(a[l]); else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void change(int id,int l,int r,int pos,int val){ if(l==r) { seg[id].val = info(val); } else { int mid = (l+r)/2; if(pos<=mid)change(id*2,l,mid,pos,val); else change(id*2+1,mid+1,r,pos,val); //重要!改完之后记得更新节点的信息 update(id); } } //O(logn) info query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].val; int mid = (l+r)/2; if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y); } } int main() { int t; cin>>t; while(t--) { int n,q; cin>>n; for(int i = 1;i<=n;i++) cin>>a[i]; memset(seg,0,sizeof(seg)); build(1,1,n); cin>>q; for(int i = 1;i<=q;i++) { ll ans = 0; int l1,r1,l2,r2; cin>>l1>>r1>>l2>>r2; if(r1<=l2)//无交集 ans = query(1,1,n,l1,r1).msuf+query(1,1,n,r1,l2).s+query(1,1,n,l2,r2).mpre-a[r1]-a[l2]; else { ans = query(1,1,n,l1,l2).msuf+query(1,1,n,l2,r1).mpre-a[l2]; ans = max(ans,query(1,1,n,l1,l2).msuf+query(1,1,n,l2,r1).s+query(1,1,n,r1,r2).mpre-a[l2]-a[r1]); ans = max(ans,query(1,1,n,l2,r1).mss); ans = max(ans,query(1,1,n,l2,r1).msuf+query(1,1,n,r1,r2).mpre-a[r1]); } cout<<ans<<endl; } } return 0; }
3.1【区间打标记,以及下传】区间加和区间最大值(eg3)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; int n,q; int a[N]; struct node{ ll t,val; }seg[N*4]; void update(int id) { seg[id].val = max(seg[id*2].val,seg[id*2+1].val); } void settag(int id,ll t) { seg[id].val = seg[id].val+t; seg[id].t = seg[id].t + t; } void pushdown(int id) { if(seg[id].t!=0) { settag(id*2,seg[id].t); settag(id*2+1,seg[id].t); seg[id].t = 0; } } void build(int id,int l,int r) { if(l==r) seg[id].val = {a[l]}; else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void modify(int id,int l,int r,int x,int y,ll t){ if(l==x&&r==y) { settag(id,t); return; } int mid = (l+r)/2; pushdown(id); if(y<=mid) modify(id*2,l,mid,x,y,t); else if(x>mid) modify(id*2+1,mid+1,r,x,y,t); else{ modify(id*2,l,mid,x,mid,t),modify(id*2+1,mid+1,r,mid+1,y,t); } update(id); } //O(logn) ll query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].val; int mid = (l+r)/2; pushdown(id); if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return max(query(id*2,l,mid,x,mid),query(id*2+1,mid+1,r,mid+1,y)); } } int main() { cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int l,r; ll d; cin>>l>>r>>d; modify(1,1,n,l,r,d); } else { int l,r; cin>>l>>r; auto ans = query(1,1,n,l,r); cout<<ans<<endl; } } return 0; }
3.2【复杂的标记问题,标记的顺序】区间加,乘,赋值(eg4)
题意:给
支持
1 l r d
,令所有的 加上 。2 l r d
,令所有的 乘上 。3 l r d
,令所有的 等于 。4 l r
,查询 。
思路:我们对于那么多操作,搞好几套标记显然是不太好的,那我们可以统一一下,把所有的
对于上述标记可以转化为:
①更新信息:
对于
原来总和是
②标记合并(有时间顺序的)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; const ll mod = 1e9+7; int n,q; int a[N]; struct tag { ll mul,add; }; tag operator+(const tag &t1,const tag &t2) { //(x*t1.mul+t1.add)*t2.mul+t2.add) return {t1.mul*t2.mul%mod,(t1.add*t2.mul+t2.add)%mod}; } struct node{ tag t; ll val; int sz; }seg[N*4]; void update(int id) { seg[id].val = (seg[id*2].val+seg[id*2+1].val)%mod; } void settag(int id,tag t) { seg[id].val = (seg[id].val*t.mul+seg[id].sz*t.add)%mod; seg[id].t = seg[id].t + t; } void pushdown(int id) { if(seg[id].t.mul!=1||seg[id].t.add!=0) { settag(id*2,seg[id].t); settag(id*2+1,seg[id].t); seg[id].t.add = 0; seg[id].t.mul = 1; } } void build(int id,int l,int r) { seg[id].t = {1,0}; seg[id].sz = r-l+1; if(l==r) seg[id].val = {a[l]}; else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void modify(int id,int l,int r,int x,int y,tag t){ if(l==x&&r==y) { settag(id,t); return; } int mid = (l+r)/2; pushdown(id); if(y<=mid) modify(id*2,l,mid,x,y,t); else if(x>mid) modify(id*2+1,mid+1,r,x,y,t); else{ modify(id*2,l,mid,x,mid,t),modify(id*2+1,mid+1,r,mid+1,y,t); } update(id); } //O(logn) ll query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].val; int mid = (l+r)/2; pushdown(id); if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return (query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y))%mod; } } int main() { cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op<=3) { int l,r,d; cin>>l>>r>>d; if(op==1) modify(1,1,n,l,r,(tag){1,d}); else if(op==2) modify(1,1,n,l,r,(tag){d,0}); else modify(1,1,n,l,r,(tag){0,d}); } else { int l,r; cin>>l>>r; auto ans = query(1,1,n,l,r); cout<<ans<<endl; } } return 0; }
3.3有关端点的区间问题
题意:
给定一个长度为 L
。
有 L
,则将 R
,否则将 L
。
对于一个只含字符 L
,R
的字符串 L
和 R
,则称
每次修改后,请输出当前序列
思路:
我们可以把L,R抽象为0、1
那么题目求的是最长01串(不存在重复的0和1)
因为不能有连续的0和连续的1,那我们在区间合并的时候就要考虑端点的值,如果一样就不能合并,否则可以,那么我们需要记录端点信息。要求最长满足的,根据最大字段和的那个题,思想差不多,记录前后缀和区间满足条件的最长长度。这里区间信息合并的时候要小心。
#include<bits/stdc++.h> using namespace std; const int N = 2e5+10; struct node { int mxl,mxr,mx; int lv,rv; int sz; }seg[N*4]; void update(int id) { seg[id].lv = seg[id*2].lv,seg[id].rv = seg[id*2+1].rv; if(seg[id*2].rv!=seg[id*2+1].lv) seg[id].mx = max({seg[id*2].mx,seg[id*2+1].mx,seg[id*2].mxr+seg[id*2+1].mxl}); else seg[id].mx = max(seg[id*2].mx,seg[id*2+1].mx); if(seg[id*2].mxl != seg[id*2].sz) seg[id].mxl = seg[id*2].mxl; else{ if(seg[id*2].rv!=seg[id*2+1].lv) seg[id].mxl = seg[id*2].mxl + seg[id*2+1].mxl; else seg[id].mxl = seg[id*2].mxl; } if(seg[id*2+1].mxr!=seg[id*2+1].sz) seg[id].mxr = seg[id*2+1].mxr; else { if(seg[id*2].rv!=seg[id*2+1].lv) seg[id].mxr = seg[id*2+1].mxr+seg[id*2].mxr; else seg[id].mxr = seg[id*2+1].mxr; } } void build(int id,int l,int r) { seg[id].sz = r-l+1; if(l==r) { seg[id].lv = seg[id].rv = 0; seg[id].mxl = seg[id].mxr = seg[id].mx = 1; return; } int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } void change(int id,int l,int r,int pos) { if(l==r) { int t = seg[id].lv^1; seg[id].lv=seg[id].rv = t; return; } int mid = (r+l)>>1; if(pos<=mid)change(id*2,l,mid,pos); else change(id*2+1,mid+1,r,pos); update(id); } int main() { int n,q; cin>>n>>q; build(1,1,n); while(q--) { int x; cin>>x; change(1,1,n,x); cout<<seg[1].mx<<endl; } return 0; }
3.4 【权值线段树】乘法原理+线段树/树状数组
题意:
在含有 thair
当且仅当
求一个序列中 thair
的个数。
思路:对于一个数,他能组成的thair
数等于它左边小于它的数的个数smaller[i]*它右边大于它的数的个数bigger[i]。
那么我们如何得知smaller[i]和biggr[i]数组呢?
我们想到之前写的逆序对的那个题,动态求解前缀和。动态更新并求解前缀和,正是树状数组的标准用法。
本题可以用两个树状数组维护或者用线段树。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 3e4+10; int a[N],cnt,s[N],smaller[N],bigger[N]; vector<int>v; map<int,int>mp; struct node { int sum; int val; }seg[N*4]; void update(int id) { seg[id].sum = seg[id*2].sum + seg[id*2+1].sum; } void change(int id,int l,int r,int pos,int t) { if(l==r) { seg[id].val += t; seg[id].sum += t; return; } int mid = (l+r)>>1; if(pos<=mid)change(id*2,l,mid,pos,t); else change(id*2+1,mid+1,r,pos,t); update(id); } int query(int id,int l,int r,int x,int y) { if(l==x&&r==y) { return seg[id].sum; } int mid = (l+r)>>1; if(y<=mid) return query(id*2,l,mid,x,y); else if(x>mid) return query(id*2+1,mid+1,r,x,y); else return query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y); } int main() { int n; cin>>n; for(int i = 1;i<=n;i++) { cin>>s[i]; v.push_back(s[i]); } sort(v.begin(), v.end()); v.erase(unique(v.begin(),v.end()),v.end()); for(auto x:v) mp[x] = ++cnt; for(int i = 1;i<=n;i++) { if(mp[s[i]]>1)smaller[i] = query(1,1,n,1,mp[s[i]]-1); //cout<<"sm = "<<smaller[i]<<endl; change(1,1,n,mp[s[i]],1); } memset(seg,0,sizeof(seg)); for(int i = n;i>=1;i--) { if(mp[s[i]]<n)bigger[i] = query(1,1,n,mp[s[i]]+1,n); //cout<<"bi = "<<bigger[i]<<endl; change(1,1,n,mp[s[i]],1); } ll ans = 0; for(int i = 1;i<=n;i++) ans+= smaller[i]*bigger[i]; cout<<ans<<endl; return 0; }
3.5 区间加和区间赋值
题意:
给定一个长度为
- 给定区间
,将区间内每个数都修改为 。 - 给定区间
,将区间内每个数都加上 。 - 给定区间
,求区间内的最大值。
思路:
本题很板,但需要注意的是区间加和区间赋值的标记下传顺序:应该是先赋值再区间加,因为如果反过来,后一个标记会覆盖前一个了。还要注意的是如果有区间赋值,区间加的标记会被清空。emmm,笨nannan我debug了好久。啊还有赋值操作要初始化为inf
#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e6+10; typedef long long ll; const ll inf = -1145141919810; struct node { ll fz,ad; int maxv,val,sz; }seg[N*4]; int a[N]; void update(int id) { seg[id].maxv = max(seg[id*2].maxv,seg[id*2+1].maxv); } void setadd(int id,ll t) { seg[id].val += t; seg[id].maxv += t; seg[id].ad += t; } void setfz(int id,ll t) { seg[id].val = t; seg[id].fz = t; seg[id].maxv = t; seg[id].ad = 0;//注意清除加标记 } void pushdown(int id) { if(seg[id].fz!=inf) { setfz(id*2,seg[id].fz); setfz(id*2+1,seg[id].fz); seg[id].fz = inf; //seg[id].ad = 0;这句是不要的,就因为它,debug了好久QAQ } if(seg[id].ad!=0) { setadd(id*2,seg[id].ad); setadd(id*2+1,seg[id].ad); seg[id].ad = 0; } } void add(int id,int l,int r,int x,int y,ll t) { if(l==x&&r==y) { setadd(id,t); return; } int mid = (l+r)>>1; pushdown(id); if(y<=mid)add(id*2,l,mid,x,y,t); else if(x>mid)add(id*2+1,mid+1,r,x,y,t); else add(id*2,l,mid,x,mid,t),add(id*2+1,mid+1,r,mid+1,y,t); update(id); } void fz(int id,int l,int r,int x,int y,ll t) { if(l==x&&r==y) { setfz(id,t); return; } int mid = (l+r)>>1; pushdown(id); if(y<=mid)fz(id*2,l,mid,x,y,t); else if(x>mid)fz(id*2+1,mid+1,r,x,y,t); else fz(id*2,l,mid,x,mid,t),fz(id*2+1,mid+1,r,mid+1,y,t); update(id); } void build(int id,int l,int r) { seg[id].ad = 0,seg[id].fz = inf;//注意赋值标记初始化为inf seg[id].sz = r-l+1; if(l==r) { seg[id].val = seg[id].maxv = a[l]; return; } int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } ll query(int id,int l,int r,int x,int y) { if(l==x&&r==y) { return seg[id].maxv; } int mid = (l+r)/2; pushdown(id); if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return max(query(id*2,l,mid,x,mid),query(id*2+1,mid+1,r,mid+1,y)); } } signed main() { ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); int n,q; cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int l,r; ll x; cin>>l>>r>>x; fz(1,1,n,l,r,x); } else if(op==2) { int l,r; ll x; cin>>l>>r>>x; add(1,1,n,l,r,x); } else{ int l,r; cin>>l>>r; cout<<query(1,1,n,l,r)<<endl; } } return 0; }
3.6 有关二进制的线段树问题
题意:
- 给定
个数的序列 。 次操作,操作有两种:- 求
。 - 把
异或上 。
- 求
, , , 。
思路:
因为是二进制按位操作,那么我们之前的写法维护节点信息是搞不了的,那怎么办呢?
我们对每个节点引入一个cnt[]数组,记录该节点二进制位的每一位有多少个1。
再看看修改操作。对于异或嘛,相同为0,相异为1。对于某一二进制位是0,如果异或0,那就不变,异或1就取反。如果某一二进制位i是1,异或0,还是1,异或1就取反。那么我们发现,无论这一位是0还是1,异或0就不变,异或1就取反,取反意味着0的个数变为1的个数,1的变0的。那我们cnt数组记录的是1的个数,如果取反就是sz-cnt[i],即节点大小-1的个数。
接下来对于询问,我们知道二进制位1的个数,那结果就是
总结:对于位运算,我们考虑一位一位来看。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5+10; int n,q; int a[N]; struct node { int cnt[20]; int sz; int t; }seg[N*4]; /* node operator+(const node&l,const node&r) { node a; for(int i = 0;i<20;i++) a.cnt[i] = l.cnt[i]+r.cnt[i]; return a; } */ //这里我写错了的重载出了问题,因为是局部变量,不能只改cnt,它的sz和t都不见了 void update(int id) { //seg[id] = seg[id*2]+seg[id*2+1]; for(int i = 0;i<20;i++) seg[id].cnt[i] = seg[id*2].cnt[i]+seg[id*2+1].cnt[i]; } void build(int id,int l,int r) { seg[id].sz = r-l+1; if(l==r) { for(int i = 0;i<20;i++) if((a[l]>>i)&1)seg[id].cnt[i] = 1; return; } int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } void settag(int id,int t) { for(int i = 0;i<20;i++) { if((t>>i)&1) seg[id].cnt[i] = seg[id].sz - seg[id].cnt[i]; } seg[id].t ^= t; } void pushdown(int id) { if(seg[id].t!=0) { settag(id*2,seg[id].t); settag(id*2+1,seg[id].t); seg[id].t = 0; } } void modify(int id,int l,int r,int x,int y,int t) { if(l==x&&r==y) { settag(id,t); return; } int mid = (r+l)>>1; pushdown(id); if(y<=mid)modify(id*2,l,mid,x,y,t); else if(x>mid)modify(id*2+1,mid+1,r,x,y,t); else modify(id*2,l,mid,x,mid,t),modify(id*2+1,mid+1,r,mid+1,y,t); update(id); } ll query(int id,int l,int r,int x,int y) { if(l==x&&r==y) { ll ret = 0,pow = 1; for(int i = 0;i<20;i++) ret += seg[id].cnt[i]*pow,pow<<=1; return ret; } int mid = (r+l)>>1; pushdown(id); if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else return query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y); } int main() { cin>>n; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); cin>>q; for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int l,r; cin>>l>>r; cout<<query(1,1,n,l,r)<<"\n"; } else { int l,r,x; cin>>l>>r>>x; modify(1,1,n,l,r,x); } } return 0; }
3.7对于区间修改有极限情况的
1.区间取模:极限情况,区间最大值<mod
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define int long long const int N = 201000; int n,q; int a[N]; struct node{ ll val,maxv; }seg[N*4]; void update(int id) { seg[id].val = seg[id*2].val+seg[id*2+1].val; seg[id].maxv = max(seg[id*2].maxv,seg[id*2+1].maxv); } void build(int id,int l,int r) { if(l==r) seg[id].val = a[l],seg[id].maxv = a[l]; else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void modify(int id,int l,int r,int x,int y,ll t){ if(l==x&&r==y) { if(seg[id].maxv>=t) { if(l==r) { seg[id].val%=t; seg[id].maxv = seg[id].val; } else { int mid = (l+r)>>1; modify(id*2,l,mid,x,mid,t); modify(id*2+1,mid+1,r,mid+1,y,t); update(id); } } return; } int mid = (l+r)/2; if(y<=mid) modify(id*2,l,mid,x,y,t); else if(x>mid) modify(id*2+1,mid+1,r,x,y,t); else{ modify(id*2,l,mid,x,mid,t),modify(id*2+1,mid+1,r,mid+1,y,t); } update(id); } void change(int id,int l,int r,int pos,int val){ if(l==r) { seg[id].val = val; seg[id].maxv = val; } else { int mid = (l+r)/2; if(pos<=mid)change(id*2,l,mid,pos,val); else change(id*2+1,mid+1,r,pos,val); update(id); } } ll query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].val; int mid = (l+r)/2; if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y); } } signed main() { int n,m; cin>>n>>m; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=m;i++) { int op; cin>>op; if(op==1) { int l,r; cin>>l>>r; cout<<query(1,1,n,l,r)<<endl; } else if(op==2) { int l,r,x; cin>>l>>r>>x; modify(1,1,n,l,r,x); } else { int k,x; cin>>k>>x; change(1,1,n,k,x); } } return 0; } /* 3 4 3 6 9 3 2 3 1 1 2 2 1 2 3 1 2 2 */
2.区间开根号:极限情况,区间最大值<=1
Can you answer these queries IV
#include<bits/stdc++.h> using namespace std; #define int long long typedef long long ll; const int N = 1e5+10; ll a[N]; struct node { ll sum,maxv; node(){}; node(int a):sum(a),maxv(a){}; }seg[N*4]; node operator+(const node&l,const node&r) { node a; a.sum = l.sum + r.sum; a.maxv = max(l.maxv,r.maxv); return a; } void update(int id) { seg[id] = seg[id*2]+seg[id*2+1]; } void build(int id,int l,int r) { if(l==r) { seg[id] = node(a[l]); return; } int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } void modify(int id,int l,int r,int x,int y) { if(l==x&&r==y) { int mid = (l+r)>>1; if(seg[id].maxv <= 1)return; else if(l==r){ ll t =sqrt(seg[id].sum); seg[id].sum = seg[id].maxv = t; } else { if(seg[id*2].maxv>1) modify(id*2,l,mid,x,mid); if(seg[id*2+1].maxv>1) modify(id*2+1,mid+1,r,mid+1,y); update(id); } return; } int mid = (l+r)>>1; if(y<=mid)modify(id*2,l,mid,x,y); else if(x>mid)modify(id*2+1,mid+1,r,x,y); else modify(id*2,l,mid,x,mid),modify(id*2+1,mid+1,r,mid+1,y); update(id); } ll query(int id,int l,int r,int x,int y) { if(l==x&&r==y) { return seg[id].sum; } int mid = (l+r)>>1; if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else return query(id*2,l,mid,x,mid)+query(id*2+1,mid+1,r,mid+1,y); } signed main() { ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); int n,q; int idx = 0; while(cin>>n) { cout<<"Case #"<<++idx<<":"<<"\n"; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); cin>>q; for(int i = 1;i<=q;i++) { int op,x,y; cin>>op>>x>>y; if(x>y)swap(x,y); if(op==0) modify(1,1,n,x,y); else cout<<query(1,1,n,x,y)<<"\n"; } } return 0; }
3.8.DFS序+线段树+奇偶性分层
题意:给一颗n个节点的树,每个节点有点权,编号为1的节点是树的根。
有m次操作:
操作1:给x加上val,然后给x的所有子节点加上-val,x的子节点的子节点加上-(-val),不断传递。
操作2:查询点x的点权。
思路:
该题是关于子树的问题,可以先将树转化为dfs序,变成序列问题再用线段树解决。
本题难点在于,每次下传val的时候,要取反。
我们设d[x]为x到根的奇偶性,0/1表示。
在修改x的点权时,在以x为根的子树中,所有与x深度奇偶性相同的点+val,所有与x深度奇偶性不同的点-val。
根据上述分析,我们考虑把奇数层和偶数层分开,建2棵树。
在修改x的时候,与x奇偶性相同的树[L,R]添加val,这样做是为了将[L,R]中与x奇偶性相同的点全部加上val
并且与x奇偶性不同的树[L,R]减少val,这样做是为了将[L,R]中与x奇偶性不同的点全部加上val
奇数树中,深度为偶数的位置是没用的,直接忽略,偶数树同理。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; int n,q; int a[N]; vector<int>edge[N]; int l[N],r[N],tot,d[N]; struct node { ll t; }; struct SegmentTree { node seg[N*4]; void settag(int id,ll t) { seg[id].val = seg[id].val+t; seg[id].t = seg[id].t + t; } void pushdown(int id) { if(seg[id].t!=0) { settag(id*2,seg[id].t); settag(id*2+1,seg[id].t); seg[id].t = 0; } } void modify(int id,int l,int r,int x,int y,ll t) { if(l==x&&r==y) { settag(id,t); return; } int mid = (l+r)/2; pushdown(id); if(y<=mid) modify(id*2,l,mid,x,y,t); else if(x>mid) modify(id*2+1,mid+1,r,x,y,t); else{ modify(id*2,l,mid,x,mid,t),modify(id*2+1,mid+1,r,mid+1,y,t); } } ll query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].val; int mid = (l+r)/2; pushdown(id); if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return max(query(id*2,l,mid,x,mid),query(id*2+1,mid+1,r,mid+1,y)); } } }T[2]; void dfs(int u,int fa,int dep) { l[u] = ++tot; d[u] = dep; for(auto v:edge[u]) { if(v==fa)continue; dfs(v,u,dep^1); } r[u] = tot; } int main() { cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; for(int i = 1;i<n;i++) { int u,v; cin>>u>>v; edge[u].push_back(v); edge[v].push_back(u); } dfs(1,0,0); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int x,val; cin>>x>>val; T[d[x]].modify(1,1,n,l[x],r[x],val); T[d[x]^1].modify(1,1,n,l[x],r[x],-val); } else { int x; cin>>x; int ans = a[x]+T[d[x]].query(1,1,n,l[x],l[x]); cout<<ans<<endl; } } return 0; }
4.线段树上二分(eg5)
给
支持
1 x d
,修改 。2 l r d
, 查询 中大于等于 的第一个数的下标,如果不存在,输出 。也就是说,求最小的 满足 。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 201000; int n,q; int a[N]; struct node{ int val; }seg[N*4]; void update(int id) { seg[id].val = max(seg[id*2].val,seg[id*2+1].val); } void build(int id,int l,int r) { if(l==r) seg[id].val = a[l]; else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void change(int id,int l,int r,int pos,int val){ if(l==r) { seg[id].val = val; return; } int mid = (l+r)/2; if(pos<=mid)change(id*2,l,mid,pos,val); else change(id*2+1,mid+1,r,pos,val); update(id); } int search(int id,int l,int r,int x,int y,int d) { if(l==x&&r==y) { if(seg[id].val<d)return -1; else { if(l==r)return l; int mid = (l+r)>>1; if(seg[id*2].val>=d)return search(id*2,l,mid,x,mid,d); else return search(id*2+1,mid+1,r,mid+1,y,d); } } int mid = (l+r)/2; if(y<=mid)return search(id*2,l,mid,x,y,d); else if(x>mid)return search(id*2+1,mid+1,r,x,y,d); else{ int pos = search(id*2,l,mid,x,mid,d); if(pos==-1) return search(id*2+1,mid+1,r,mid+1,y,d); else return pos; } } int main() { cin>>n>>q; for(int i = 1;i<=n;i++) cin>>a[i]; build(1,1,n); for(int i = 1;i<=q;i++) { int op; cin>>op; if(op==1) { int x,d; cin>>x>>d; change(1,1,n,x,d); } else { int l,r,d; cin>>l>>r>>d; auto ans = search(1,1,n,l,r,d); cout<<ans<<endl; } } return 0; }
5.区间最值操作(吉司机线段树)
[吉司机的ppt][[https://files.cnblogs.com/files/wawawa8/Segment_tree_Beats!.pdf]
笼统地来说,区间最值操作指,将区间
eg1.Gorgeous Sequence
维护一个序列 a:
1.0 l r t,对于任意i属于l到r,a[i] = min(a[i],t);
2.1 l r 输出区间 [l,r] 中的最大值。
3.2 l r 输出区间和。
思路:
对于区间取min,意味着该区间内>t的数要变。也就是说,操作不是对整个区间,而是【区间内大于t的数】。
那么我们需要维护的信息是:区间最大值,次大值,区间和,区间最大值的个数。
TIPS:
我们可以换种方式来考虑:把线段树上每一个节点的最大值看成是区间取min标记,次大值看成是子树中标记的最大值。因为每次更新只会更新到(区间次大值<t<区间最大值)的情况,然后取修改max和sum。一旦进入子树,就必须先更新子树的sum和max,就需要先pushdown,把当前区间信息传给子树。
对于
- 如果区间最大值都比
小,那么整个操作无意义,直接 即可。 - 如果次大值比
小,最大值比t大,我们只需要更新区间最大值为 。并且更新区间和为 ,并打上一个标记。 - 如果次大值小于等于
,那我们不能确定有多少个要被更新。那我们就暴力递归下去(搜索它的子树),然后再 信息回来。
时间复杂度:
#include<bits/stdc++.h> #pragma GCC optimize(2) using namespace std; typedef long long ll; const int N = 1e6+10; char nc() { static char buf[1000000], *p = buf, *q = buf; return p == q && (q = (p = buf) + fread(buf, 1, 1000000, stdin), p == q) ? EOF : *p++; } int rd() { int res = 0; char c = nc(); while (!isdigit(c)) c = nc(); while (isdigit(c)) res = res * 10 + c - '0', c = nc(); return res; } ll n,q; int a[N]; struct node{ ll sum; int mx,se; int t,cnt; }seg[N*4]; void update(int id) { const int ls = id<<1,rs = id<<1|1; seg[id].sum = seg[ls].sum + seg[rs].sum; if(seg[ls].mx==seg[rs].mx) { seg[id].mx = seg[ls].mx; seg[id].se = max(seg[ls].se,seg[rs].se); seg[id].cnt = seg[ls].cnt+seg[rs].cnt; } else if(seg[ls].mx>seg[rs].mx) { seg[id].mx = seg[ls].mx; seg[id].se = max(seg[ls].se,seg[rs].mx); seg[id].cnt = seg[ls].cnt; } else { seg[id].mx = seg[rs].mx; seg[id].se = max(seg[ls].mx,seg[rs].se); seg[id].cnt = seg[rs].cnt; } } void settag(int id,int t) { if(seg[id].mx<=t)return; seg[id].sum += (1ll*t - seg[id].mx)*seg[id].cnt; seg[id].mx = seg[id].t = t; } void pushdown(int id) { if(seg[id].t!=-1) { settag(id*2,seg[id].t); settag(id*2+1,seg[id].t); seg[id].t = -1; } } void build(int id,int l,int r) { seg[id].t =-1; if(l==r) seg[id].mx = seg[id].sum = a[l],seg[id].cnt = 1,seg[id].se = -1; else { int mid = (l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); update(id); } } void modify(int id,int l,int r,int x,int y,ll t){ if(seg[id].mx<=t)return; if(x==l&&r==y&&seg[id].se<t)return settag(id,t); int mid = (l+r)>>1; pushdown(id); if(y<=mid) modify(id*2,l,mid,x,y,t); else if(x>mid) modify(id*2+1,mid+1,r,x,y,t); else{ modify(id*2,l,mid,x,mid,t),modify(id*2+1,mid+1,r,mid+1,y,t); } update(id); } int query(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].mx; int mid = (l+r)>>1; pushdown(id); if(y<=mid)return query(id*2,l,mid,x,y); else if(x>mid)return query(id*2+1,mid+1,r,x,y); else{ return max(query(id*2,l,mid,x,mid),query(id*2+1,mid+1,r,mid+1,y)); } } ll query2(int id,int l,int r,int x,int y) { if(l==x&&r==y)return seg[id].sum; int mid = (l+r)>>1; pushdown(id); if(y<=mid)return query2(id*2,l,mid,x,y); else if(x>mid)return query2(id*2+1,mid+1,r,x,y); else{ return query2(id*2,l,mid,x,mid)+query2(id*2+1,mid+1,r,mid+1,y); } } int main() { int t; t = rd(); while(t--) { n = rd(), q = rd(); for(int i = 1;i<=n;i++) a[i] = rd(); build(1,1,n); for(int i = 1;i<=q;i++) { int op; op = rd(); if(op==0) { int l,r; int d; l = rd(), r = rd(), d = rd(); modify(1,1,n,l,r,d); } else if(op==1) { int l,r; l = rd(), r = rd(); printf("%d\n",query(1,1,n,l,r)); } else { int l,r; l = rd(), r = rd(); printf("%lld\n",query2(1,1,n,l,r)); } } } return 0; }
eg2(吉司机线段树模板题).BZOJ4695 最假女选手
给定一个长度为 N序列,编号从1 到 N。要求支持下面几种操作:
1.给一个区间[L,R] 加上一个数x
2.把一个区间[L,R] 里小于x 的数变成x
3.把一个区间[L,R] 里大于x 的数变成x
4.求区间[L,R] 的和
5.求区间[L,R] 的最大值
6.求区间[L,R] 的最小值
思路:
跟上一题同样的方法,我们取维护:最大mx,次大mx2,最小mn,次小mn2的值,和最大cmx和最小cmn的个数,区间和sum。还需要维护区间取min(tmn),取max和区间加的标记(tmx)。这里就会涉及到标记下传的顺序了。
策略:
- 认为区间加优先级最大,取min和max一样
- 对于某个节点加一个t标记,除了用t更新区间和信息,还需要用整个t更新区间取max和取min
- 对于一个节点取min,除了更新区间和信息,还有注意与区间max的标记比较。如果t小于区间max的标记,则最后所有数都会变成t,那么吧区间max的标记也变成t,否则不管。
细节:
- 注意取min和取max时候的特判,一个数可能即是最大值又是次小值这种(代码里有写)。
- 卡常,加快读
时间复杂度:
#include <cstdio> #include <iostream> using namespace std; typedef long long ll; int rd() { char act = 0; int f = 1, x = 0; while (act = getchar(), act < '0' && act != '-') ; if (act == '-') f = -1, act = getchar(); x = act - '0'; while (act = getchar(), act >= '0') x = x * 10 + act - '0'; return x * f; } const int N = 5e5 + 5, SZ = N << 2, inf = 0x7fffffff; int n,m; int a[N]; struct node { int mx,mx2,mn,mn2,cmx,cmn,tmx,tmn,t; ll sum; }seg[N * 4]; void update(int id) { const int ls = id<<1,rs = id<<1|1; seg[id].sum = seg[ls].sum + seg[rs].sum; if(seg[ls].mx==seg[rs].mx) { seg[id].mx = seg[ls].mx,seg[id].cmx = seg[ls].cmx+seg[rs].cmx; seg[id].mx2 = max(seg[ls].mx2,seg[rs].mx2); } else if(seg[ls].mx>seg[rs].mx) { seg[id].mx = seg[ls].mx,seg[id].cmx = seg[ls].cmx; seg[id].mx2 = max(seg[ls].mx2,seg[rs].mx); } else { seg[id].mx = seg[rs].mx,seg[id].cmx = seg[rs].cmx; seg[id].mx2 = max(seg[ls].mx,seg[rs].mx2); } if(seg[ls].mn==seg[rs].mn) { seg[id].mn = seg[ls].mn,seg[id].cmn = seg[ls].cmn+seg[rs].cmn; seg[id].mn2 = min(seg[ls].mn2,seg[rs].mn2); } else if(seg[ls].mn<seg[rs].mn) { seg[id].mn = seg[ls].mn,seg[id].cmn = seg[ls].cmn; seg[id].mn2 = min(seg[ls].mn2,seg[rs].mn); } else { seg[id].mn = seg[rs].mn,seg[id].cmn = seg[rs].cmn; seg[id].mn2 = min(seg[ls].mn,seg[rs].mn2); } } void push_add(int id,int l,int r,int t) { //更新加法标记的同时更新取min和取max的标记 seg[id].sum += (r-l+1ll)*t; seg[id].mx += t,seg[id].mn += t; if(seg[id].mx2 != -inf)seg[id].mx2 += t; if (seg[id].mn2 != inf)seg[id].mn2 += t; if(seg[id].tmx != -inf)seg[id].tmx += t; if(seg[id].tmn != inf)seg[id].tmn += t; seg[id].t += t; } void push_min(int id,int t) { //取min的时候不仅是改最大值,最小值也可能改,注意比较取max的标记 if(seg[id].mx<=t)return; seg[id].sum += (t*1ll-seg[id].mx)*seg[id].cmx; if(seg[id].mn2 == seg[id].mx)//次小等于最大 seg[id].mn2 = t; if(seg[id].mn==seg[id].mx)//最小等于最大 seg[id].mn = t; if(seg[id].tmx>t)seg[id].tmx = t;//更新取max标记 seg[id].mx = t,seg[id].tmn = t; } void push_max(int id,int t) { if(seg[id].mn>t)return; seg[id].sum += (t*1ll-seg[id].mn)*seg[id].cmn; if(seg[id].mx2 == seg[id].mn)//次大等于最小 seg[id].mx2 = t; if(seg[id].mx==seg[id].mn)//最小等于最大 seg[id].mx = t; if(seg[id].tmn<t)seg[id].tmn = t;//更新取min标记 seg[id].mn = t,seg[id].tmx = t; } void pushdown(int id,int l,int r) { const int ls = id<<1,rs = id<<1|1,mid = (l+r)>>1; if(seg[id].t) push_add(ls,l,mid,seg[id].t),push_add(rs,mid+1,r,seg[id].t); if(seg[id].tmx != -inf)push_max(ls,seg[id].tmx),push_max(rs,seg[id].tmx); if(seg[id].tmn != inf)push_min(ls,seg[id].tmn),push_min(rs,seg[id].tmn); seg[id].t = 0,seg[id].tmx = -inf,seg[id].tmn = inf; } void build(int id,int l,int r) { seg[id].tmn = inf,seg[id].tmx = -inf; if(l==r) { seg[id].sum = seg[id].mx = seg[id].mn = a[l]; seg[id].mx2 = -inf,seg[id].mn2 = inf; seg[id].cmx = seg[id].cmn = 1; return; } int mid = (l+r)>>1; build(id*2,l,mid),build(id*2+1,mid+1,r); update(id); } void add(int id,int l,int r,int x,int y,int t) { if(y < l || r < x)return; if(x==l&&r==y)return push_add(id,l,r,t);//!!注意是return int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) add(id*2,l,mid,x,y,t); else if(x>mid) add(id*2+1,mid+1,r,x,y,t); else{ add(id*2,l,mid,x,mid,t),add(id*2+1,mid+1,r,mid+1,y,t); } update(id); } void tomin(int id,int l,int r,int x,int y,int t) { if(y < l || r < x||seg[id].mx<=t)return; if(x==l&&r==y&&seg[id].mx2<t)return push_min(id,t); int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) tomin(id*2,l,mid,x,y,t); else if(x>mid) tomin(id*2+1,mid+1,r,x,y,t); else{ tomin(id*2,l,mid,x,mid,t),tomin(id*2+1,mid+1,r,mid+1,y,t); } update(id); } void tomax(int id,int l,int r,int x,int y,int t) { if(y < l || r < x||seg[id].mn>=t)return; if(x<=l&&r<=y&&seg[id].mn2>t)return push_max(id,t); int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) tomax(id*2,l,mid,x,y,t); else if(x>mid) tomax(id*2+1,mid+1,r,x,y,t); else{ tomax(id*2,l,mid,x,mid,t),tomax(id*2+1,mid+1,r,mid+1,y,t); } update(id); } ll qsum(int id,int l,int r,int x,int y) { if(y < l || r < x)return 0; if(x<=l&&r<=y)return seg[id].sum; int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) qsum(id*2,l,mid,x,y); else if(x>mid) qsum(id*2+1,mid+1,r,x,y); else{ return qsum(id*2,l,mid,x,mid)+qsum(id*2+1,mid+1,r,mid+1,y); } } ll qmax(int id,int l,int r,int x,int y) { if(y < l || r < x)return -inf; if(x<=l&&r<=y)return seg[id].mx; int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) qmax(id*2,l,mid,x,y); else if(x>mid) qmax(id*2+1,mid+1,r,x,y); else{ return max(qmax(id*2,l,mid,x,mid),qmax(id*2+1,mid+1,r,mid+1,y)); } } ll qmin(int id,int l,int r,int x,int y) { if(y < l || r < x)return inf; if(x<=l&&r<=y)return seg[id].mn; int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) qmin(id*2,l,mid,x,y); else if(x>mid) qmin(id*2+1,mid+1,r,x,y); else{ return min(qmin(id*2,l,mid,x,mid),qmin(id*2+1,mid+1,r,mid+1,y)); } } int main() { n = rd(); for(int i = 1;i<=n;i++)a[i] = rd(); build(1,1,n); m = rd(); for(int i = 1;i<=m;i++) { int op,l,r,x; op = rd(),l = rd(),r = rd(); if(op<=3) x = rd(); if(op==1) add(1,1,n,l,r,x); else if(op==2) tomax(1,1,n,l,r,x); else if(op==3) tomin(1,1,n,l,r,x); else if(op==4) printf("%lld\n",qsum(1,1,n,l,r)); else if(op==5) printf("%lld\n",qmax(1,1,n,l,r)); else printf("%lld\n",qmin(1,1,n,l,r)); } return 0; }
eg3.Mzl loves segment tree
两个序列$ A,B
对
对 $A
每次操作完后,如果
思路:
先考虑最容易的区间加,因为只要加的不是
对于区间最值操作:
我们本质上将序列数分成三类:最大值、最小值、非最值进行维护。我们在打标记的时候顺便给
这种操作本质上就是把最值信息拿去给
eg4.CTSN loves segment tree
两个序列
对
对
对
对
询问区间的
我们把区间中的 位置 分成四类:在
6.历史最值问题
历史最值不等于可持久化
历史最值一般可分为三类:历史最大值、历史最小值、历史版本和。
历史最大值
简单地说,一个位置的历史最大值就是当前位置下曾经出现过的数的最大值。形式化地定义,我们定义一个辅助数组
这时,我们将
eg.P6242 【模板】线段树 3
eg.P4314 CPU 监控
序列 A,B 一开始相同:
- 对 A 做区间覆盖
- 对 A 做区间加
- 询问 A 的区间
- 询问 B 的区间
每次操作后,我们都进行一次更新,
思路:
我们先不考虑操作一,先只考虑区间加操作,我们维护区间加
因为要维护区间历史最大值,所以对每一个区间我们都需要维护一个新的值表示在更新这个区间的时产生的历史最大值,设为
对于生存周期:
对于一个标记会经历:
- 节点
被建立 - 节点
接受若干个新标记的同时与新标记合并 - 节点
标记下传给儿子, 的标记被清空
我们认为从
定义生存周期的意义?
因为一个节点的子节点在一个标记的生存周期内不会发生任何变化,并且保留这个周期之前的状态。因为在这个期间是没有标记下传的。
于是:当前标记生存周期内的历史
接下来,把操作一考虑进来:
对于区间覆盖操作,会把所有数变为同一个。因此我们可以把第一次区间覆盖后的所有标记都看成区间覆盖标记。
我们发现,对于一个点,首次赋值操作之后的任何修改都可以看作赋值操作,因为这样区间的所有数都相同了,若实现区间加,则直接看为对区间赋值的改变即可。
也就是说,一个标记的生产周期大致分为两个阶段:
- 若干个加减标记的合并,未接收过覆盖标记
- 覆盖标记(若干加减标记被转化为覆盖标记)
根据上述,于是我们把节点的
时间复杂度
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll inf = 1<<30; char nc() { static char buf[1000000], *p = buf, *q = buf; return p == q && (q = (p = buf) + fread(buf, 1, 1000000, stdin), p == q) ? EOF : *p++; } ll rd() { ll s = 0, w = 1; char ch = nc(); while (ch < '0' || ch > '9') { if (ch == '-') w = -1; ch = nc(); } while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = nc(); return s * w; } const int N = 2e5 + 10; struct node { int mx,_mx; int ad,_ad; int st,_st; }seg[N*4]; int a[N],n,m; void update(int id) { seg[id].mx = max(seg[id*2].mx,seg[id*2+1].mx); seg[id]._mx = max(seg[id*2]._mx,seg[id*2+1]._mx); } void pushadd(int id,int v,int _v) { seg[id]._mx = max(seg[id]._mx,seg[id].mx+_v),seg[id].mx += v; /* 重要!!! 判断该区间是否已经有赋值操作了。 若有则接下来的区间加改为对区间赋值操作 若没有就继续进行区间加操作 */ if(seg[id].st == -inf) seg[id]._ad = max(seg[id]._ad,seg[id].ad+_v),seg[id].ad+= v; else seg[id]._st = max(seg[id]._st,seg[id].st + _v),seg[id].st += v; } void pushset(int id,int v,int _v) { seg[id]._mx = max(seg[id]._mx,_v),seg[id].mx = v; seg[id]._st = max(seg[id]._st,_v),seg[id].st = v; } void pushdown(int id,int l,int r) { if(seg[id].ad||seg[id]._ad) { pushadd(id*2,seg[id].ad,seg[id]._ad),pushadd(id*2+1,seg[id].ad,seg[id]._ad); seg[id].ad = seg[id]._ad = 0; } if(seg[id].st!=-inf||seg[id]._st!=-inf) { pushset(id*2,seg[id].st,seg[id]._st),pushset(id*2+1,seg[id].st,seg[id]._st); seg[id].st = seg[id]._st = -inf; } } void build(int id,int l,int r) { seg[id].st = seg[id]._st = -inf; if(l==r) { seg[id].mx = seg[id]._mx = a[l]; return; } int mid = (l+r)>>1; build(id*2,l,mid),build(id*2+1,mid+1,r); update(id); } void add(int id,int l,int r,int x,int y,int v) { if(y<l||r<x)return; if(l==x&&r==y)return pushadd(id,v,max(v,0)); pushdown(id,l,r); int mid = (l+r)>>1; if(y<=mid) add(id*2,l,mid,x,y,v); else if(x>mid) add(id*2+1,mid+1,r,x,y,v); else{ add(id*2,l,mid,x,mid,v),add(id*2+1,mid+1,r,mid+1,y,v); } update(id); } void test(int id,int l,int r,int x,int y,int v) { if(y<l||r<x)return; if(l==x&&r==y)return pushset(id,v,v); pushdown(id,l,r); int mid = (l+r)>>1; if(y<=mid) test(id*2,l,mid,x,y,v); else if(x>mid) test(id*2+1,mid+1,r,x,y,v); else{ test(id*2,l,mid,x,mid,v),test(id*2+1,mid+1,r,mid+1,y,v); } update(id); } int qmax(int id,int l,int r,int x,int y) { if(y<l||r<x)return -inf; if(l==x&&r==y)return seg[id].mx; int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) return qmax(id*2,l,mid,x,y); else if(x>mid) qmax(id*2+1,mid+1,r,x,y); else{ return max(qmax(id*2,l,mid,x,mid),qmax(id*2+1,mid+1,r,mid+1,y)); } } int qmaxh(int id,int l,int r,int x,int y) { if(y<l||r<x)return -inf; if(l==x&&r==y)return seg[id]._mx; int mid = (l+r)>>1; pushdown(id,l,r); if(y<=mid) return qmaxh(id*2,l,mid,x,y); else if(x>mid) qmaxh(id*2+1,mid+1,r,x,y); else{ return max(qmaxh(id*2,l,mid,x,mid),qmaxh(id*2+1,mid+1,r,mid+1,y)); } } int main() { n = rd(); for(int i = 1;i<=n;i++)a[i] = rd(); build(1,1,n); m = rd(); for(int i = 1;i<=m;i++) { char op = nc(); while (op == ' ' || op == '\r' || op == '\n') op = nc(); int l,r,x; l = rd(),r = rd(); if(op=='Q') printf("%d\n",qmax(1,1,n,l,r)); else if(op=='A') printf("%d\n",qmaxh(1,1,n,l,r)); else if(op=='P') { x = rd(); add(1,1,n,l,r,x); } else { x = rd(); test(1,1,n,l,r,x); } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!