题解:P4747 [CERC2017] Intrinsic Interval
kkksc03wzl · · 题解
真是一道美妙的思维好题
前言
今天高三学长给我们讲了这道题目,感觉十分巧妙,写一篇题解记录一下。
STEP 0
据说这道题有各种各样的高科技做法,比如说析合树、建图跑,但我通通不会。 这里讲一个非常震撼的小清新做法。(其实也是多数人写这道题的做法)
STEP 1
这种类型的区间要求看上去很不可做,所以我们先考虑把它离线下来,按照一定的扫描顺序去跑。但是具体怎么跑需要我们进一步对“好区间”的性质进行分析。
STEP 2
注意到!
两个好区间的交一定也是个好区间。
因为好区间的下标编号和值域连续的,所以作为连续的序列编号和连续的值域,它们的交也一定会是连续的。
注意到!!
一个区间
这个等价条件其实是比较好理解的,但是没有逆天注意力谁能想到,,
当然,它还有一个等价条件是
STEP 3
我们注意了这么多,总该考虑实现了吧。。
因为两个好区间的交也是好区间,所以如果有两个好区间
于是我们以
初始时我们把线段树上每个点赋成
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
int p[100005],ansl[100005],ansr[100005],id[100005];
struct que{
int l,r,id;
bool operator<(const que x)const{return l<x.l;}
}a[100005];
bool cmp(que a,que b){return a.r<b.r;}
struct node{int mn,laz,pos;}tr[100005<<2];
//一颗平平常常的记录最小值位置的线段树
void pushup(int rt){
tr[rt].mn=min(tr[lson].mn,tr[rson].mn);
if(tr[rson].mn<=tr[lson].mn)tr[rt].pos=tr[rson].pos;
else tr[rt].pos=tr[lson].pos;
}void pushdown(int rt){
int k=tr[rt].laz;
if(k){
tr[lson].laz+=k,tr[lson].mn+=k;
tr[rson].laz+=k,tr[rson].mn+=k;
tr[rt].laz=0;
}
}void update(int rt,int l,int r,int x,int y,int k){
if(x<=l&&r<=y)return tr[rt].laz+=k,tr[rt].mn+=k,void();
int mid=l+r>>1;
pushdown(rt);
if(x<=mid)update(lson,l,mid,x,y,k);
if(y>mid)update(rson,mid+1,r,x,y,k);
pushup(rt);
}int query(int rt,int l,int r,int x,int y){
if(x<=l&&r<=y)return tr[rt].mn==0?tr[rt].pos:-47;
int ans=-47,mid=l+r>>1;
pushdown(rt);
if(x<=mid)ans=max(ans,query(lson,l,mid,x,y));
if(y>mid)ans=max(ans,query(rson,mid+1,r,x,y));
return ans;
}void build(int rt,int l,int r){
tr[rt].laz=0;
if(l==r)return tr[rt].mn=-l,tr[rt].pos=l,void();
int mid=l+r>>1;
build(lson,l,mid),build(rson,mid+1,r);
pushup(rt);
}priority_queue<que>now;//因为每一次询问的答案的右端点不确定,所以我们要开一个以左端点为序的优先队列
int main(){
int n,m;scanf("%d",&n);
for(int i=1; i<=n; i++)scanf("%d",&p[i]),id[p[i]]=i;//记录排列中每一种数字的出现位置
scanf("%d",&m);
for(int i=1; i<=m; i++)scanf("%d%d",&a[i].l,&a[i].r),a[i].id=i;
sort(a+1,a+1+m,cmp);
int pos=1;
build(1,1,n);
for(int r=1; r<=n; r++){
while(pos<=m&&a[pos].r<=r)now.push(a[pos++]);//把新产生的要处理的询问压到优先队列里
update(1,1,n,1,n,1);
if(p[r]!=1&&id[p[r]-1]<r)update(1,1,n,1,id[p[r]-1],-1);
if(p[r]!=n&&id[p[r]+1]<r)update(1,1,n,1,id[p[r]+1],-1);//对新产生的连续数对的贡献的计算
int tt=now.size();
for(int i=0; i<tt; i++){
que t=now.top();
int as=query(1,1,n,1,t.l);
if(as>0){//如果答案合法就记录
ansl[t.id]=as,ansr[t.id]=r;
now.pop();
}else{//否则就直接停掉,因为以左端点为序,所以合法性单调
break;
}
}
}for(int i=1; i<=m; i++){
printf("%d %d\n",ansl[i],ansr[i]);
}
return 0;
}//完结撒花 ^_^