其他分享
首页 > 其他分享> > Prokletnik

Prokletnik

作者:互联网

link

一道很好的线段树题目。

题外话:今天上午,全宇宙内最弱的辣鸡 Feyn 再一次和正解擦肩而过,下午这个蒟蒻看题解甚至没有看懂题解的写法(参考题解链接,我实在没看出来它的两棵线段树咋维护的,为啥觉得它只写了一棵呢,果然我还是太弱了),于是这个连题解都看不懂的菜鸡花了一下午时间研究了一种低效但比较清晰的写法,希望可以提供一些帮助(虽然觉得应该不会有人需要这篇题解,因为大家看完那篇题解应该就都会了)。

本人在考场上想出了一个 \(O(N^2)\) 的做法,对于每一个区间,枚举哪个点作为答案区间的右端点,然后考虑如何选择左端点使这个区间尽量的长。分两种情况,假如希望右端点是区间的最小值,那么显然选中的左端点应该在上一个比这个右端点小的位置的后面。同时由于规定了左端点是区间的最大值,那么这个区间内不应该出现比左点更大的值,所以左端点不应该比当前询问区间的最大值的位置还靠左。两个限制需要同时满足,所以取较大值,和枚举的这个右端点一起去更新答案即可。左大右小也是一样。由于呢写得太丑了考场上只混到了 20 分,就很悲惨。

正解其实和上面的方法有共通之处。还是先考虑左小右大(另一种情况把每个数取反再跑一次即可),由于在线维护似乎比较麻烦,所以可以把所有询问离线下来然后按右端点排序,每次加入一个右端点。按照惯常的思路应该有一个数据结构,数据结构上每个点的值代表这个点作为左端点时最大的区间长度,然后查询这个询问区间的答案就只需要查询数据结构中询问区间的最大值就可以啦。这样显然是正确的,因为所有右端点大于当前询问的右端点的区间都还没有被加入,而所有左端点小于询问的左端点的区间无法被访问,所以这个思路是正确的。

但问题来了,如何维护这个东西呢。

显然有些位置的答案是不会变化的,因为变化的位置需要满足一个条件,即这个位置和当前枚举到的右端点可以形成一个合法的区间(为了叙述方便,一个魔法序列称为一个合法的区间)。这需要满足什么条件呢,区间内不能有比当前右端点大的,也不能有比左端点小的。假如当前的右端点是 \(x\) ,那么一个合法的 \(y\) 需要满足 $\forall y\le i\le x,a_y\le a_i\le a_x $。有人说这不废话吗,那么我们可以换一种表示方式。假如 \(px\) 是 \(x\) 之前第一个比它大的数的位置,\(py\) 是 \(y\) 之后第一个比它小的位置,那么有 \(y\ge px+1,x\le py-1\)。这就是单调栈的节奏了。对于后面的那个限制 \(x\le py-1\),可以用单调栈来维护,用简单的方式使得栈内所有元素都符合这个条件,这样一来可能被更新的位置就是那些在栈内的、而且满足 \(y\ge px+1\) 的那些元素。由于栈是上大下小的结构,根据前面的条件,可能被更新的元素实际上就是栈内某个位置(二分即可)到栈顶的所有元素,用线段树更新即可。

那那些不会被更新的位置咋办呢?可以发现如果之前存在一个 \(x'\ge py\),那么随着 \(x\) 的增大,上面那个不等式会一直成立下去,所以这个位置就再也不会被更新了。于是就可以把那些位置的数丢出来装在另一棵线段树内查询即可。那如何找出这些位置呢?上面说了维护单调栈的时候不是要弹出元素吗,弹出的每一个元素都是不会被更新的啊。

再补充说明一点上面那个维护栈内答案的线段树。由于元素间的实际下标可能并不连续,我在这棵树里用的是栈的下标。而这棵线段树支持的修改操作比较离谱,它要求支持的操作是这样的:由于树上的每片叶子都对应的原序列中的一个位置,而每次修改的时候都会给出另一个位置(也就是当前枚举的右端点),希望每个叶子的答案修改成所谓“另一个位置”减去叶子对应位置再加一(也就是叶子对应位置和右端点形成的序列长度)。lazy 标记好说,那如何更新答案呢?思考上面的那个柿子,所谓“另一个位置”是不变的,所以要求区间内的最大值,实际上就是要求某个区间内所有叶子对应位置的最小值,在结点内更新即可(写法为,每个节点直接继承左儿子的那个答案)。

我是把两棵线段树完全分开写的,感觉会更清晰一点,代码有注释。话说为啥我题解又写了一个小时。。。

#include<bits/stdc++.h>
//#define feyn
const int N=500010;
using namespace std;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
inline int max(int s1,int s2){
	return s1<s2?s2:s1;
}
inline int min(int s1,int s2){
	return s1<s2?s1:s2;
}

#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1) 
//x tree是废弃结点对应的线段树 
namespace x{
	struct node{
		int l,r,data;
	}t[N<<2];
	inline void pushup(int wh){
		t[wh].data=max(t[lc].data,t[rc].data);
	}
	inline void build(int wh,int l,int r){
		t[wh].l=l,t[wh].r=r,t[wh].data=0;
		if(l==r)return;
		build(lc,l,mid);
		build(rc,mid+1,r);
	}
	inline void update(int wh,int pl,int val){
		if(t[wh].l==t[wh].r){
			t[wh].data=val;return;
		}
		update(pl<=mid?lc:rc,pl,val); 
		pushup(wh);
	}
	inline int work(int wh,int wl,int wr){
		if(wl<=t[wh].l&&t[wh].r<=wr)return t[wh].data;
		int an=0;
		if(wl<=mid)an=max(an,work(lc,wl,wr));
		if(wr>mid)an=max(an,work(rc,wl,wr));
		return an;
	}
}
//y是栈内元素对应的线段树 
namespace y{
	struct node{
		int l,r;
		int lazy,data,pl_min;
		//data是区间内权值最大的左端点的权值
		//pl_min是区间内在原序列中对应位置最靠左的位置 
	}t[N<<2];
	inline void pushup(int wh){
		t[wh].pl_min=t[lc].pl_min;//直接继承左儿子 
		t[wh].data=max(t[lc].data,t[rc].data);
	}
	inline void pushnow(int wh,int val){
		if(val<=t[wh].lazy)return;
		t[wh].lazy=val;
		t[wh].data=max(t[wh].data,val-t[wh].pl_min+1);//更新答案 
	}
	inline void pushdown(int wh){
		if(t[wh].lazy){
			pushnow(lc,t[wh].lazy);
			pushnow(rc,t[wh].lazy);
			t[wh].lazy=0;
		}
	}
	inline void build(int wh,int l,int r){
		t[wh].l=l,t[wh].r=r,t[wh].data=t[wh].pl_min=t[wh].lazy=0;
		if(l==r)return;
		build(lc,l,mid);
		build(rc,mid+1,r);
	}
	inline void update(int wh,int pl,int val){
		if(t[wh].l==t[wh].r){
			t[wh].data=1;t[wh].lazy=0;t[wh].pl_min=val;return;
		}
		pushdown(wh);
		update(pl<=mid?lc:rc,pl,val);
		pushup(wh);
	}
	inline void change(int wh,int wl,int wr,int val){
		if(wl<=t[wh].l&&t[wh].r<=wr){
			pushnow(wh,val);return;
		}
		pushdown(wh);
		if(wl<=mid)change(lc,wl,wr,val);
		if(wr>mid)change(rc,wl,wr,val);
		pushup(wh);
	}
	inline int work(int wh,int wl,int wr){
		if(wl<=t[wh].l&&t[wh].r<=wr){
			return t[wh].data;
		}
		int an=0;pushdown(wh);
		if(wl<=mid)an=max(an,work(lc,wl,wr));
		if(wr>mid)an=max(an,work(rc,wl,wr));
		pushup(wh);return an;
	}
}
#undef lc
#undef rc
#undef mid


struct node{
	int l,r,id;
}c[N];
inline bool cmp(node s1,node s2){
	return s1.r<s2.r;
}
int m,n,a[N],an[N];

int st[N],top;
stack<int>st_max;
void solve(){
	a[0]=1e9+10;
	while(!st_max.empty())st_max.pop();
	st_max.push(top=0);
	x::build(1,1,m);y::build(1,1,m);
	for(int i=1,j=1;i<=m;i++){
		while(a[st_max.top()]<=a[i])st_max.pop();
		int pl=st_max.top();st_max.push(i);
		//find first one bigger than a[i]
		while(top&&a[st[top]]>a[i]){
			int now_pl=st[top--];
			int now_data=y::work(1,top+1,top+1);
			x::update(1,now_pl,now_data);
		}
		st[++top]=i;y::update(1,top,i);
		int fir=lower_bound(st+1,st+top+1,pl)-st;
		y::change(1,fir,top,i);
		//find ones smaller than a[i]
		while(j<=n&&c[j].r==i){
			int now_pl=lower_bound(st+1,st+top+1,c[j].l)-st;
			an[c[j].id]=max(an[c[j].id],y::work(1,now_pl,top));
			an[c[j].id]=max(an[c[j].id],x::work(1,c[j].l,c[j].r));
			j++;
		}
	}
}

signed main(){
	
	#ifdef feyn
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);
	for(int i=1;i<=m;i++)read(a[i]);
	read(n);
	for(int i=1;i<=n;i++){
		read(c[i].l);read(c[i].r);
		c[i].id=i;
	}
	sort(c+1,c+n+1,cmp);
	solve();
	for(int i=1;i<=m;i++)a[i]=-a[i];
	solve();
	for(int i=1;i<=n;i++)printf("%d\n",an[i]);
	
	return 0;
}

标签:int,Prokletnik,top,位置,wh,端点,区间
来源: https://www.cnblogs.com/dai-se-can-tian/p/16518382.html