[BZOJ]5259: [Cerc2017]区间 线段树

Description

给定一个1到n的排列a1, . . . , an。
对于一个区间[l, r],我们称该区间是连续的,如果将al, . . . , ar排列之后得到的是一列连续的数。
(换句话说,如果x,y都在该区间中,那么所有介于x,y之间的数也在该区间中)
现在有m(1 ≤ n, m ≤ 100000)个询问,每个询问给出一个区间[xi, yi],
你需要找到一个长度最短的连续区间[li,ri],使得[xi,yi]属于 [li, ri]。

Solution

考虑如何判断一个区间是连续段,当且仅当区间内 ( x , x + 1 ) (x,x+1) (x,x+1)的对数为 r − l r-l rl
设区间 [ l , r ] [l,r] [l,r] ( x , x + 1 ) (x,x+1) (x,x+1)的对数为 c ( l , r ) c(l,r) c(l,r)
我们可以枚举右端点 r r r,用线段树维护 l + c ( l , r ) l+c(l,r) l+c(l,r)。可以发现合法仅当 l + c ( l , r ) = r l+c(l,r)=r l+c(l,r)=r,并且 l + c ( l , r ) l+c(l,r) l+c(l,r)最大值为 r r r,所以只需要维护最大值以及最大值的位置就可以了。
实现的时候把所有询问离线,枚举到了询问的 r r r端点就把询问丢进一个优先队列里面,以询问的 l l l端点为关键字,堆顶是 l l l最大的。每次如果能找到答案就pop,否则就break,因为查询的是 [ 1 , l ] [1,l] [1,l]的最大值, l l l越大一定越容易找到答案。

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
#define pa pair<int,int>
#define LL long long
const int Maxn=100010;
const int inf=2147483647;
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int n,a[Maxn],pos[Maxn],m;
struct Seg{int lc,rc,l,r,mx,tag,pos;}tr[Maxn<<1];
int tot=0;
void Add(int x,int v){tr[x].mx+=v,tr[x].tag+=v;}
void up(int x)
{
	int lc=tr[x].lc,rc=tr[x].rc;
	if(tr[rc].mx>=tr[lc].mx)tr[x].pos=tr[rc].pos;
	else tr[x].pos=tr[lc].pos;
	tr[x].mx=max(tr[lc].mx,tr[rc].mx);
}
void down(int x)
{
	int lc=tr[x].lc,rc=tr[x].rc,t=tr[x].tag;
	if(t)tr[x].tag=0,Add(lc,t),Add(rc,t);
}
void build(int l,int r)
{
	int x=++tot;
	tr[x].l=l;tr[x].r=r;tr[x].tag=0;
	if(l==r){tr[x].pos=tr[x].mx=l;return;}
	int mid=l+r>>1;
	tr[x].lc=tot+1,build(l,mid);
	tr[x].rc=tot+1,build(mid+1,r);
	up(x);
}
void add(int x,int l,int r,int v)
{
	if(tr[x].l==l&&tr[x].r==r){Add(x,v);return;}
	int lc=tr[x].lc,rc=tr[x].rc,mid=tr[x].l+tr[x].r>>1;
	down(x);
	if(r<=mid)add(lc,l,r,v);
	else if(l>mid)add(rc,l,r,v);
	else add(lc,l,mid,v),add(rc,mid+1,r,v);
	up(x);
}
int Mx,Pos;
void query(int x,int l,int r)
{
	if(tr[x].l==l&&tr[x].r==r)
	{
		if(tr[x].mx>=Mx)Mx=tr[x].mx,Pos=tr[x].pos;
		return;
	}
	int lc=tr[x].lc,rc=tr[x].rc,mid=tr[x].l+tr[x].r>>1;
	down(x);
	if(r<=mid)query(lc,l,r);
	else if(l>mid)query(rc,l,r);
	else query(lc,l,mid),query(rc,mid+1,r);
}
struct P{int l,id;P(int _l,int _id){l=_l,id=_id;}};
bool operator<(P a,P b){return a.l<b.l;}
vector<P>h[Maxn];
priority_queue<P>q;
int ans[Maxn][2];
int main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	build(1,n);
	m=read();
	for(int i=1;i<=m;i++)
	{
		int l=read(),r=read();
		h[r].push_back(P(l,i));
	}
	for(int i=1;i<=n;i++)
	{
		int x=a[i];
		if(x!=1&&pos[x-1])add(1,1,pos[x-1],1);
		if(x!=n&&pos[x+1])add(1,1,pos[x+1],1);
		pos[x]=i;
		for(int j=0;j<h[i].size();j++)q.push(h[i][j]);
		while(!q.empty())
		{
			P t=q.top();
			Mx=-inf;
			query(1,1,t.l);
			if(Mx!=i)break;
			q.pop();
			ans[t.id][0]=Pos,ans[t.id][1]=i;
		}
	}
	for(int i=1;i<=m;i++)printf("%d %d\n",ans[i][0],ans[i][1]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值