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
题意:
给定一个序列。查询左端点在\([x_1, y_1]\)之间,且右端点在\([x_2, y_2]\)之间的最大子段和,数据保证\(x_1\leq x_2,y_1\leq y_2\) ,但是不保证端点所在的区间不重合。
思路:分类讨论。
第一种情况:没有两个区间没有相交部分。
第二种情况:有相交部分。
再分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)
题意:给\(n\)个数\(a_1,a_2,a_3,…,a_n\)。
支持\(q\)个操作:
1 l r d
,令所有的\(a_i(l≤i≤r)\)加上\(d\)。2 l r d
,令所有的\(a_i(l≤i≤r)\)乘上\(d\)。3 l r d
,令所有的\(a_i(l≤i≤r)\)等于\(d\)。4 l r
,查询\((\sum_{i = l}^{r})\bmod (109+7)\)。
思路:我们对于那么多操作,搞好几套标记显然是不太好的,那我们可以统一一下,把所有的\(a[i]\)变成\(a[i]*mul+add\)的标记,一开始默认$mul = 1,add = 0 $。
对于上述标记可以转化为:
- \(*1+d\)
- \(*d+0\)
- \(*0+d\)
①更新信息:
对于\(a1,a1...an\)
原来总和是\(s\)。\(ai——>ai*x+y\),那么\(s——>s*x+size*y\)
②标记合并(有时间顺序的)
#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有关端点的区间问题
题意:
给定一个长度为 \(n\) 的字符序列 \(a\),初始时序列中全部都是字符 L
。
有 \(q\) 次修改,每次给定一个 \(x\),若 \(a_x\) 为 L
,则将 \(a_x\) 修改成 R
,否则将 \(a_x\) 修改成 L
。
对于一个只含字符 L
,R
的字符串 \(s\),若其中不存在连续的 L
和 R
,则称 \(s\) 满足要求。
每次修改后,请输出当前序列 \(a\) 中最长的满足要求的连续子串的长度。
思路:
我们可以把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 【权值线段树】乘法原理+线段树/树状数组
题意:
在含有 \(n\) 个整数的序列 \(a_1,a_2,\ldots,a_n\) 中,三个数被称作thair
当且仅当 \(i<j<k\) 且 \(a_i<a_j<a_k\)。
求一个序列中 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 区间加和区间赋值
题意:
给定一个长度为 \(n\) 的序列 \(a\),要求支持如下三个操作:
- 给定区间 \([l, r]\),将区间内每个数都修改为 \(x\)。
- 给定区间 \([l, r]\),将区间内每个数都加上 \(x\)。
- 给定区间 \([l, r]\),求区间内的最大值。
思路:
本题很板,但需要注意的是区间加和区间赋值的标记下传顺序:应该是先赋值再区间加,因为如果反过来,后一个标记会覆盖前一个了。还要注意的是如果有区间赋值,区间加的标记会被清空。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 有关二进制的线段树问题
题意:
- 给定 \(n\) 个数的序列 \(a\)。\(m\) 次操作,操作有两种:
- 求 \(\displaystyle\sum_{i=l}^r a_i\)。
- 把 \(a_l\sim a_r\) 异或上 \(x\)。
- \(1\le n\le 10^5\),\(1\le m\le 5\times 10^4\),\(0\le a_i\le 10^6\),\(1\le x\le 10^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的个数,那结果就是\(\sum\)每一位的权值*该位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)
给\(n\)个数\(a_1,a_2,a_3,…,a_n\)。
支持\(q\)个操作:
1 x d
,修改\(ax=d\)。2 l r d
, 查询\(a_l,a_{l+1},a_{l+2},…,a_r\)中大于等于\(d\)的第一个数的下标,如果不存在,输出\(−1\)。也就是说,求最小的\(i(l≤i≤r)\)满足\(a_i≥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]
笼统地来说,区间最值操作指,将区间\([l,r]\)的数全部对\(x\)取 \(min\) 或 \(max\),即 \(a_i = min(a_i,x)\) 或\(a_i = min(a_i,x)\)。
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\)取\(min\),我们讨论一下:
- 如果区间最大值都比\(t\)小,那么整个操作无意义,直接\(return\)即可。
- 如果次大值比\(t\)小,最大值比t大,我们只需要更新区间最大值为\(t\)。并且更新区间和为\(sum+cnt*(t-max)\),并打上一个标记。
- 如果次大值小于等于\(t\),那我们不能确定有多少个要被更新。那我们就暴力递归下去(搜索它的子树),然后再\(update\)信息回来。
时间复杂度:\(O(mlogn)\)
#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时候的特判,一个数可能即是最大值又是次小值这种(代码里有写)。
- 卡常,加快读
时间复杂度:\(O(nlogn+mlog^2n)\)
#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\(,一开始\) B$ 中的数是$ 0$。维护的操作是:
对$ A$ 做区间取$ \min$
对 $A \(做区间取\) \max\(
对\) A$ 做区间加
询问$ B $的区间和
每次操作完后,如果 \(A_i\) 的值发生变化,就给$ B_i $加\(1\)。\(n,m\le 3\times 10^5\)。
思路:
先考虑最容易的区间加,因为只要加的不是\(0\),那\(A\)就发生了变化,给\(B\)做一次区间加。
对于区间最值操作:
我们本质上将序列数分成三类:最大值、最小值、非最值进行维护。我们在打标记的时候顺便给\(B\)更新信息(不是给\(B\)打标记是更新信息!)。查询的时候,我们在\(A\)上查询,下传标记的时候顺便给\(B\)更新信息。找到需要的节点之后返回\(B\)的信息即可。
这种操作本质上就是把最值信息拿去给\(B\)维护了,另外还是要特判,注意数集重复问题!
eg4.CTSN loves segment tree
两个序列$ A,B$:
对$ A$ 做区间取 \(\min\)
对 \(B\) 做区间取 \(\min\)
对$ A$ 做区间加
对$ B$ 做区间加
询问区间的 $A_i+B_i $的最大值
\(n,m\le 3\times 10^5。\)
我们把区间中的 位置 分成四类:在$ A,B$ 中同是区间最大值的位置、在 \(A\) 中是区间最大值在$ B$ 中不是的位置、在$ B$ 中是区间最大值在 $A $中不是的位置、在 $A,B \(中都不是区间最大值的位置。对这四类数分别维护 **答案** 和 **标记** 即可,此外维护一下 A 的最大值、次大值、最大值标记、所有值标记,同理 *B* 的。举个例子,我们维护\) C_{1\sim 4},M_{1\sim 4},A_{\max},B_{\max}$ 分别表示当前区间中四类数的个数,四类数的答案的最大值,\(A\) 序列的最大值、\(B\) 序列的最大值。然后合并信息该怎么合并就怎么合并了。
6.历史最值问题
历史最值不等于可持久化
历史最值一般可分为三类:历史最大值、历史最小值、历史版本和。
历史最大值
简单地说,一个位置的历史最大值就是当前位置下曾经出现过的数的最大值。形式化地定义,我们定义一个辅助数组 \(B\),一开始与$ A$ 完全相同。在 \(A\) 的每次操作后,我们对整个数组取 \(\max:\)
\(\forall i\in[1,n],\ B_i=\max(B_i,A_i)\)
这时,我们将 $B_i $称作这个位置的历史最大值,
eg.P6242 【模板】线段树 3
eg.P4314 CPU 监控
序列 A,B 一开始相同:
- 对 A 做区间覆盖$ x$
- 对 A 做区间加 \(x\)
- 询问 A 的区间$ \max$
- 询问 B 的区间$ \max$
每次操作后,我们都进行一次更新,\(\forall i\in [1,n],\ B_i=\max(B_i,A_i)。n,m\le 10^5。\)
思路:
我们先不考虑操作一,先只考虑区间加操作,我们维护区间加\(Add\)标记,该标记可以解决区间\(max\)的问题。
因为要维护区间历史最大值,所以对每一个区间我们都需要维护一个新的值表示在更新这个区间的时产生的历史最大值,设为\(pre\)。该标记的含义是:在该标记的生存周期内,\(Add\) 标记的历史最大值。
对于生存周期:
对于一个标记会经历:
- 节点\(u\)被建立
- 节点\(u\)接受若干个新标记的同时与新标记合并
- 节点\(u\)标记下传给儿子,\(u\)的标记被清空
我们认为从\(1到3\)之前都是节点\(u\)标记的生存周期,当两个标记被合并了,称为同一个标记了,他们的生存周期也认为合并了。也就是说,生存周期表示:从上次把该节点标记下传的时刻到当前时刻。
定义生存周期的意义?
因为一个节点的子节点在一个标记的生存周期内不会发生任何变化,并且保留这个周期之前的状态。因为在这个期间是没有标记下传的。
于是:当前标记生存周期内的历史\(Add\)的最大值是可以更新到子节点的标记和信息上的。因为此期间内其子节点的标记和信息均没变过,于是我们把该节点\(u\)的标记传给它的儿子\(s\),可以得到:
\(pre_s = max(prs_s,pre_s+Add),Add_s = Add_u + Add_s\)
接下来,把操作一考虑进来:
对于区间覆盖操作,会把所有数变为同一个。因此我们可以把第一次区间覆盖后的所有标记都看成区间覆盖标记。
我们发现,对于一个点,首次赋值操作之后的任何修改都可以看作赋值操作,因为这样区间的所有数都相同了,若实现区间加,则直接看为对区间赋值的改变即可。
也就是说,一个标记的生产周期大致分为两个阶段:
- 若干个加减标记的合并,未接收过覆盖标记
- 覆盖标记(若干加减标记被转化为覆盖标记)
根据上述,于是我们把节点的\(Pre\)标记拆为两个阶段(\(P1,P2\)),其中\(P1\)表示第一阶段最大加减标记,\(P2\)表示第二阶段最大覆盖标记。
时间复杂度\(O(m\log n)\)
#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;
}