Loading

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;
}
posted @ 2021-12-16 11:38  Alan_Zhao_2007  阅读(70)  评论(0编辑  收藏  举报