2017-2018 ACM-ICPC, Central Europe Regional Contest (CERC 17) I Intrinsic Interval

题目

给你一个排列,定义一个连续段为一个子区间,其中包含的数是连续的。

对于一个子区间,求出包含它的本源连续段,即包含它的最小的连续段。

需要处理若干个询问。

n , m ≤ 1 0 5 n,m\le 10^5 n,m105


正解

一眼看下去这不就是析合树吗?

于是调了半天析合树,最终艰难地通过了。

当然有些比较阳间的做法:可以发现对于询问的区间 [ L , R ] [L,R] [L,R],找到最小的 r r r使得存在 l l l [ L , R ] ⊆ [ l , r ] [L,R]\subseteq [l,r] [L,R][l,r]并且 [ l , r ] [l,r] [l,r]为连续段,此时 [ l , r ] [l,r] [l,r]即为答案。

反证为什么是对的:如果存在更优的区间 [ l ′ , r ′ ] [l',r'] [l,r] r < r ′ r<r' r<r,那么一定有 [ l ′ , r ] [l',r] [l,r]为连续段(两个连续段的交为连续段)。

后面就随便做了:右端点 r r r从左到右扫,每个询问挂在右端点上,用个线段树对于每个 l l l维护 m a x i = l r a i − min ⁡ i = l r + l max_{i=l}^r a_i-\min_{i=l}^r+l maxi=lraimini=lr+l最小的 l l l,如果这个值等于 r r r,那么 [ l , r ] [l,r] [l,r]为连续段。用个 s e t set set存每个询问,如果当前存在最长的连续段 [ l , r ] [l,r] [l,r]覆盖了某个询问,那么这个询问答案的右端点就确定了,左端点在线段树上二分就可以出来。

之前 m a x i = l r a i − min ⁡ i = l r + l max_{i=l}^r a_i-\min_{i=l}^r+l maxi=lraimini=lr+l一直都是维护两个单调栈,然而我AC之后看网上的题解发现一个似乎更方便的做法:一个连续段 [ l , r ] [l,r] [l,r]一定满足有 r − l r-l rl ( x , x + 1 ) (x,x+1) (x,x+1)满足 x , x + 1 x,x+1 x,x+1都在区间内。考虑统计有多少对。于是在加入某个数 x x x的时候,包含 x − 1 x-1 x1 x + 1 x+1 x+1的区间就可以加一。维护个最大值。其它的类似。

时间复杂度都是 O ( n lg ⁡ n ) O(n\lg n) O(nlgn)。(不过最正宗的析合树配上tarjan LCA可以做到线性)


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
int n;
int a[N];
int s0[N],t0;
int s1[N],t1;
pair<int,int> v[N*4];
int t[N*4];
pair<int,int> vmin(pair<int,int> x,pair<int,int> y){return x.first<=y.first?x:y;}
void build(int k,int l,int r){
	v[k]={0,l};
	if (l==r) return;
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
void add(int k,int l,int r,int st,int en,int c){
	if (st<=l && r<=en){
		v[k].first+=c,t[k]+=c;
		return;
	}
	if (t[k]){
		v[k<<1].first+=t[k],t[k<<1]+=t[k];
		v[k<<1|1].first+=t[k],t[k<<1|1]+=t[k];
		t[k]=0;
	}
	int mid=l+r>>1;
	if (st<=mid) add(k<<1,l,mid,st,en,c);
	if (mid<en) add(k<<1|1,mid+1,r,st,en,c);
	v[k]=vmin(v[k<<1],v[k<<1|1]);
}
pair<int,int> res;
void query(int k,int l,int r,int st,int en){
	if (v[k].first>=res.first) return;
	if (st<=l && r<=en){
		res=v[k];
		return;
	}
	if (t[k]){
		v[k<<1].first+=t[k],t[k<<1]+=t[k];
		v[k<<1|1].first+=t[k],t[k<<1|1]+=t[k];
		t[k]=0;
	}
	int mid=l+r>>1;
	if (st<=mid) query(k<<1,l,mid,st,en);
	if (mid<en) query(k<<1|1,mid+1,r,st,en);
}
int f[N];
struct Node{
	Node *fa,*r;
	int mn,mx;
	bool s;//0 xi 1 he
	int L,R;
} d[N*2];
int cnt;
Node *id[N];
bool judge(Node *x,Node *y){
	return x->mx-x->mn+1+y->mx-y->mn+1==max(x->mx,y->mx)-min(x->mn,y->mn)+1;
}
void eat(Node *x,Node *y){
	x->mn=min(x->mn,y->mn);
	x->mx=max(x->mx,y->mx);
	x->L=min(x->L,y->L);
	x->R=max(x->R,y->R);
}
Node *q[N];
int nq;
void insert(Node *x,int l){
	if (!nq || x->L<=l){
		q[++nq]=x;
		return;
	}
	Node *t=q[nq];
	if (judge(t,x)){
		if (t->r && judge(t->r,x)){
			x->fa=t,t->r=x;
			eat(t,x);
			--nq;
			insert(t,l);
		}
		else{
			Node *nw=&d[++cnt];
			*nw={NULL,x,min(t->mn,x->mn),max(t->mx,x->mx),1,t->L,x->R};
			t->fa=x->fa=nw;
			--nq;
			insert(nw,l);
		}
	}
	else{
		t=&d[++cnt];
		x->fa=t;
		*t={NULL,x,x->mn,x->mx,0,x->L,x->R};
		while (nq && q[nq]->R>=l){
			q[nq]->fa=t;
			eat(t,q[nq--]);
			if (t->mx-t->mn+1==t->R-t->L+1){
				insert(t,l);
				break;
			}
		}
	}
}
int dep[N*2],fa[N*2][18];
void getdep(Node *t){
	if (dep[t-d]) return;
	if (t->fa==NULL){
		dep[t-d]=1;
		return;
	}
	getdep(t->fa);
	dep[t-d]=dep[t->fa-d]+1;
	fa[t-d][0]=t->fa-d;
	for (int i=1;1<<i<dep[t-d];++i)	
		fa[t-d][i]=fa[fa[t-d][i-1]][i-1];
}
int LCA(int u,int v){
	if (dep[u]<dep[v]) swap(u,v);
	for (int k=dep[u]-dep[v],i=0;k;k>>=1,++i)
		if (k&1)
			u=fa[u][i];
	if (u==v) return u;
	for (int i=17;i>=0;--i)
		if (fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
int kth(int u,int k){
	for (int i=0;k;k>>=1,++i)
		if (k&1)
			u=fa[u][i];
	return u;
}
int main(){
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	build(1,1,n);
	for (int i=1;i<=n;++i){
		while (t0 && a[i]<a[s0[t0]]){
			add(1,1,n,s0[t0-1]+1,s0[t0],+a[s0[t0]]);
			--t0;
		}
		add(1,1,n,s0[t0]+1,i,-a[i]);
		s0[++t0]=i;
		while (t1 && a[i]>a[s1[t1]]){
			add(1,1,n,s1[t1-1]+1,s1[t1],-a[s1[t1]]);
			--t1;
		}
		add(1,1,n,s1[t1]+1,i,+a[i]);
		s1[++t1]=i;
		add(1,1,n,i,i,i);
		res={i+1,0};
		query(1,1,n,1,i);
		f[i]=res.second;
	}
//	for (int i=1;i<=n;++i)
//		printf("%d ",f[i]);
//	printf("\n");
	for (int i=1;i<=n;++i){
		id[i]=&d[++cnt];
		*id[i]={NULL,NULL,a[i],a[i],0,i,i};
		insert(id[i],f[i]);
//		for (int j=1;j<=nq;++j)
//			printf("%d:[%d,%d] ",q[j]-d,q[j]->L,q[j]->R);
//		printf("\n");
	}
	for (int i=1;i<=cnt;++i){
		getdep(&d[i]);
//		printf("%d ",d[i].fa-d);
	}
//	printf("\n");
	int T;
	scanf("%d",&T);
	while (T--){
		int l,r;
		scanf("%d%d",&l,&r);
		l=id[l]-d;
		r=id[r]-d;
		int lca=LCA(l,r);
		if (d[lca].s==0)
			printf("%d %d\n",d[lca].L,d[lca].R);
		else{
			l=kth(l,dep[l]-dep[lca]-1);
			r=kth(r,dep[r]-dep[lca]-1);
			printf("%d %d\n",d[l].L,d[r].R);
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值