CF997E Good Subsegments - 扫描线、线段树
题意
给定一个排列 \(\{p_n\}\),\(q\) 次询问一个区间 \([l_i,r_i]\) 中有多少个子区间是值域连续段。
题解
对于一个区间 \([l,r]\),设 \(f(l,r)=(\max_{l\le i\le r} p_i-\min_{l\le i\le r} p_i)-(r-l)\)。那么 \([l,r]\) 是值域连续段当且仅当 \(f(l,r)=0\)。
将所有区间放在坐标系里,以 \(l\) 为纵轴,\(r\) 为横轴,那么对于每一个位置 \(i\),以它为最大或最小值的所有区间形成了一个矩形。于是,我们可以通过 \(\mathcal{O}(n)\) 次矩形加,维护出所有 \(f(l,r)\)。
我们用扫描线扫右端点。由于 \(p\) 是个排列,所以 \(f(l,r)\ge 0\)。因此,查询区间中 \(0\) 的个数相当于查最小值个数。同时,一次查询在坐标系上是一个矩形查询,因此还需要在线段树上维护历史信息之和。
对于那 \(\mathcal{O}(n)\) 次矩形加,可以在右端点变化时,用单调栈算出所有最大、最小值发生变化的区间,然后在线段树上区间加即可。
历史标记的下传有点奇怪……
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <stack>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
typedef long long ll;
const int N=1.2e5+5,LogN=17,Inf=0x3f3f3f3f;
struct SegmentTree{
struct Node{
int l,r,mn,mcnt,Add;ll hist,Hist;
Node operator+(const Node &rs) const{
if(!l) return rs; if(!rs.l) return *this;
int Mn=min(mn,rs.mn),Mcnt=0;
if(mn==Mn) Mcnt+=mcnt;
if(rs.mn==Mn) Mcnt+=rs.mcnt;
return {l,rs.r,Mn,Mcnt,0,hist+rs.hist,0};
}
void PushAdd(int k){mn+=k,Add+=k;}
void PushHist(ll k){hist+=k*mcnt,Hist+=k;}
}t[N<<2];
void Pushdown(int p){
if(t[p].Add) t[p*2].PushAdd(t[p].Add),t[p*2+1].PushAdd(t[p].Add),t[p].Add=0;
if(t[p].Hist){
if(t[p].mn==t[p*2].mn) t[p*2].PushHist(t[p].Hist);
if(t[p].mn==t[p*2+1].mn) t[p*2+1].PushHist(t[p].Hist);
t[p].Hist=0;
}
}
void Build(int p,int l,int r){
t[p].l=l,t[p].r=r,t[p].mn=Inf;
if(l==r){t[p].mn=l,t[p].mcnt=1;return;}
Build(p*2,l,(l+r)/2),Build(p*2+1,(l+r)/2+1,r);
t[p]=t[p*2]+t[p*2+1];
}
void Add(int p,int l,int r,int k){
if(l>t[p].r||r<t[p].l) return;
if(l<=t[p].l&&t[p].r<=r) return t[p].PushAdd(k);
Pushdown(p);
Add(p*2,l,r,k),Add(p*2+1,l,r,k);
t[p]=t[p*2]+t[p*2+1];
}
Node Query(int p,int l,int r){
if(l>t[p].r||r<t[p].l) return Node();
if(l<=t[p].l&&t[p].r<=r) return t[p];
Pushdown(p);
return Query(p*2,l,r)+Query(p*2+1,l,r);
}
}seg;
int n,p[N],q;
struct Query{int i,l,r;}qry[N];
int smin[N],smax[N];ll ans[N];
int main(){
ios::sync_with_stdio(false),cin.tie(nullptr);
cin>>n;
For(i,1,n) cin>>p[i];
seg.Build(1,1,n);
cin>>q;
For(i,1,q){
cin>>qry[i].l>>qry[i].r;qry[i].i=i;
}
sort(qry+1,qry+q+1,[](const Query &q1,const Query &q2){return q1.r<q2.r;});
int tp1=0,tp2=0;
for(int i=1,j=1;i<=n;++i){
seg.t[1].PushAdd(-1);
while(tp2&&p[smax[tp2]]<p[i]){
seg.Add(1,smax[tp2-1]+1,smax[tp2],p[i]-p[smax[tp2]]);
smax[tp2--]=0;
}
smax[++tp2]=i;
while(tp1&&p[smin[tp1]]>p[i]){
seg.Add(1,smin[tp1-1]+1,smin[tp1],p[smin[tp1]]-p[i]);
smin[tp1--]=0;
}
smin[++tp1]=i;
seg.t[1].PushHist(1);
for(;j<=q&&qry[j].r==i;++j) ans[qry[j].i]=seg.Query(1,qry[j].l,qry[j].r).hist;
}
For(i,1,q) cout<<ans[i]<<'\n';
return 0;
}
Written by Alan_Zhao