Codeforces 1332G - No Monotone Triples(数据结构综合)

Codeforces 题目传送门 & 洛谷题目传送门

首先打表即可发现对于任意长度 \(\ge 5\) 的序列总存在一个 Monotone triple,证明不会实在不行直接 \(5^5\) 枚举相对位置大小都可以发现,因此答案要么是 \(0\),要么是 \(3\),要么是 \(4\),我们考虑对三种情况分别分类讨论,即检查区间内是否存在长度为 \(3\) 的 Monotone triple free 的子序列,再检查区间内是否存在长度为 \(4\) 的 Monotone triple free 的子序列,这样即可确定答案。

因此我们可以大致将题目分为两个部分:

1. 检验区间中是否存在长度为 \(3\) 的 Monotone triple free 的子序列

首先显然三个位置 \(i,j,k(i<j<k)\) 能够构成 Monotone triple free 的序列当且仅当中间位置的值严格小于边上两个数的值,或者中间位置的值严格大于边上两个数的值。

考虑枚举中间位置 \(p\),我们贪心地想,我们希望这三个位置都在区间 \([l,r]\) 中,因此边上两个位置肯定离中间位置越紧凑越好,因此我们可以先预处理出四个数组:\(mxl_i\) 表示在 \(i\) 前面且大于 \(a_i\) 的离 \(i\) 最近的位置,\(mxr_i\) 表示在 \(i\) 后面且大于 \(a_i\) 的离 \(i\) 最近的位置,\(mnl_i,mnr_i\) 也可以类似地定义,单调栈可以求出。那么对于以 \(p\) 为中心的长度为 \(3\) 的序列,\((mnl_p,p,mnr_p),(mxl_p,p,mxr_p)\) 必然都是的 Monotone triple free 的序列,并且这两个序列分别是上述两种情况中最紧凑的序列——如果它们都不能包含在 \([l,r]\) 中那么所有以 \(p\) 为中心的长度为 \(3\) 的 Monotone triple free 序列肯定都不能包含在 \([l,r]\) 中了。

因此题目转化为,是否 \(\exists p\in[l,r]\) 满足 \(mnl_p\ge l,mnr_p\le r\) 或者 \(mxl_p\ge l,mxr_p\le r\)。考虑一个离线做法,我们将所有询问都挂在 \(l\) 处,然后开一个指针 \(i\) 从右到左扫描,再建立一棵线段树,每次当指针从 \(i+1\) 变到 \(i\) 时都枚举所有 \(mnl_p=i\)\(p\) 并用 \(mnr_p\) 更新线段树上 \(p\) 位置上的值,\(mxl\) 也同理,回答询问时只需检验线段树上 \([l,r]\) 位置上的最小值是否 \(\le r\) 即可,输出方案只需再记录一个取到最小值的位置再稍微分下类即可。

时间复杂度 \(n\log n\)

2. 检验区间中是否存在长度为 \(4\) 的 Monotone triple free 的子序列

这个看起来有亿点点棘手,因此我们先来挖掘下性质,对于一个长度为 \(4\) 的序列而言,如果它的最大值不唯一那肯定不合法——如果这两个最大值有一个在中间两个位置就肯定存在一个三元组满足左边两个数或右边两个数相同,否则不论中间两个数大小关系如何也都会存在 monotone triple。因此这四个数只能有唯一的最大值和最小值,而如果这唯一的最大值/最小值在两边,不妨以最大值在第一个位置为例,如果后三个数存在逆序对那这个最大值与这个逆序对就存在 monotone triple,否则这三个数就构成了 monotone triple,因此四个数构成 Monotone triple free 序列的充要条件是:存在唯一的最大/小值,并且最大/小值分别在第二、三个位置。

我们考虑固定第一、四个位置 \(i,l\),那么我们肯定会选择区间 \((i,l)\) 的最大值&最小值作为第二、三个位置,即存在合法的序列当且仅当区间 \((i,l)\) 的最大值 \(>\max(a_i,a_l)\),且最小值 \(<\min(a_i,a_l)\),直接枚举肯定行不通。我们考虑预处理 \(r3_i\) 表示最小的 \(r\) 满足 \((i,r]\) 最大值 \(>a_i\) 且最小值 \(<a_i\)\(l3_i\) 表示最大的 \(l\) 满足 \([l,i)\) 最大值 \(>a_i\) 且最小值 \(<a_i\),这个可以二分+ST 表,也可以直接调用上面求出的 \(mnl_i,mnr_i,mxl_i,mxr_i\),那么当我们固定住 \(i\) 之后,合法的 \(l\) 必须 \(>r3_i\),并且 \(l3_l>i\)

我们再求出 \(r4_i\) 表示最小的 \(l\) 满足 \(l>r3_i,l3_l>i\),这个可以通过对 \(l3\) 数组建 ST 表之后二分求出,那么 \(i,r4_i\) 恰好分别对应一个长度为 \(4\) 的 Monotone triple free 序列的 \(i,l\),并且这肯定是以 \(i\) 为第一个元素的最后一个位置下标最小的合法序列,因此对于每个询问我们需检验是否存在一个 \(i\in[l,r]\) 满足 \(r4_i\le r\),这个可以再对 \(r4\) 建 ST 表求出。最后输出方案可以维护一个取到最小值的位置求出边上两个元素,再根据这两个位置之间最大值、最小值的位置求出中间两个元素。

时间复杂度 \(n\log n\)

那么问题就来了,为什么求长度为 \(3\) 的序列时离线了,求长度为 \(4\) 的序列时反而没有离线呢

const int MAXN=2e5;
const int LOG_N=17;
const int INF=0x3f3f3f3f;
int n,qu,a[MAXN+5],L[MAXN+5],R[MAXN+5];
vector<pii> ql[MAXN+5];
int mnl[MAXN+5],mxl[MAXN+5],mnr[MAXN+5],mxr[MAXN+5];
namespace solve3{
	vector<pii> pl[MAXN+5];
	tuple<int,int,int> ans[MAXN+5];
	struct node{int l,r;pii val;} s[MAXN*4+5];
	pii merge(pii x,pii y){
		pii ret;ret.fi=min(x.fi,y.fi);
		if(ret.fi==x.fi) ret.se=x.se;
		else ret.se=y.se;
		return ret;
	}
	void pushup(int k){s[k].val=merge(s[k<<1].val,s[k<<1|1].val);}
	void build(int k,int l,int r){
		s[k].l=l;s[k].r=r;if(l==r){s[k].val=mp(INF,l);return;}
		int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);pushup(k);
	}
	void modify(int k,int x,int v){
		if(s[k].l==s[k].r) return chkmin(s[k].val.fi,v),void();
		int mid=s[k].l+s[k].r>>1;
		(x<=mid)?modify(k<<1,x,v):modify(k<<1|1,x,v);
		pushup(k);
	}
	pii query(int k,int l,int r){
		if(l<=s[k].l&&s[k].r<=r) return s[k].val;
		int mid=s[k].l+s[k].r>>1;
		if(r<=mid) return query(k<<1,l,r);
		else if(l>mid) return query(k<<1|1,l,r);
		else return merge(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
	}
	void solve(){
		build(1,1,n);
		for(int i=1;i<=n;i++) pl[mxl[i]].pb(mp(i,mxr[i]));
		for(int i=1;i<=n;i++) pl[mnl[i]].pb(mp(i,mnr[i]));
//		for(int i=1;i<=n;i++) printf("%d %d %d %d\n",mnl[i],mxl[i],mnr[i],mxr[i]);
		for(int i=n;i;i--){
			for(pii p:pl[i]) modify(1,p.fi,p.se);
			for(pii p:ql[i]){
				int x=p.fi,id=p.se;
				pii mn=query(1,i,x);
				if(mn.fi<=x){
					int pos=mn.se;
					if(mnl[pos]>=i&&mnr[pos]<=x) ans[id]=make_tuple(mnl[pos],pos,mnr[pos]);
					if(mxl[pos]>=i&&mxr[pos]<=x) ans[id]=make_tuple(mxl[pos],pos,mxr[pos]);
				}
			}
		}
	}
}
namespace solve4{
	template<typename T> struct st_table{
		T v[MAXN+5][LOG_N+2];int op;
		void build(){
			for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n;j++)
				v[j][i]=(op)?max(v[j][i-1],v[j+(1<<i-1)][i-1]):min(v[j][i-1],v[j+(1<<i-1)][i-1]);
		}
		T query(int l,int r){
			int k=31-__builtin_clz(r-l+1);
			return (op)?max(v[l][k],v[r-(1<<k)+1][k]):min(v[l][k],v[r-(1<<k)+1][k]);
		}
	};
	st_table<int> st3;
	st_table<pii> mn,mx,st4;
	int l3[MAXN+5],r3[MAXN+5],r4[MAXN+5];
	tuple<int,int,int,int> ans[MAXN+5];
	void solve(){
		mx.op=st3.op=1;
		for(int i=1;i<=n;i++) mn.v[i][0]=mx.v[i][0]=mp(a[i],i);
		mn.build();mx.build();
		for(int i=1;i<=n;i++){
			r3[i]=max(mxr[i],mnr[i]);
			l3[i]=min(mxl[i],mnl[i]);
			st3.v[i][0]=l3[i];
		} st3.build();
		for(int i=1;i<=n;i++){
			int l=r3[i]+1,r=n,p=n+1;
			while(l<=r){
				int mid=l+r>>1;
				if(st3.query(r3[i]+1,mid)>i) p=mid,r=mid-1;
				else l=mid+1;
			} r4[i]=p;st4.v[i][0]=mp(r4[i],i);
//			printf("%d\n",r4[i]);
		} st4.build();
		for(int i=1;i<=qu;i++){
			pii p=st4.query(L[i],R[i]);
//			printf("%d\n",p.fi);
			if(p.fi<=R[i]){
				int i1=p.se,i4=p.fi;
				int i2=mn.query(i1,i4).se,i3=mx.query(i1,i4).se;
				if(i2>i3) i2^=i3^=i2^=i3;
//				printf("%d %d %d %d\n",i1,i2,i3,i4);
				ans[i]=make_tuple(i1,i2,i3,i4);
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&qu);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	stack<int> stk;
	for(int i=1;i<=n;i++){
		while(!stk.empty()&&a[stk.top()]<=a[i]) stk.pop();
		mxl[i]=((stk.empty())?0:stk.top());stk.push(i);
	} while(!stk.empty()) stk.pop();
	for(int i=1;i<=n;i++){
		while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
		mnl[i]=((stk.empty())?0:stk.top());stk.push(i);
	} while(!stk.empty()) stk.pop();
	for(int i=n;i;i--){
		while(!stk.empty()&&a[stk.top()]<=a[i]) stk.pop();
		mxr[i]=((stk.empty())?n+1:stk.top());stk.push(i);
	} while(!stk.empty()) stk.pop();
	for(int i=n;i;i--){
		while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
		mnr[i]=((stk.empty())?n+1:stk.top());stk.push(i);
	} while(!stk.empty()) stk.pop();
	for(int i=1;i<=qu;i++) scanf("%d%d",&L[i],&R[i]),ql[L[i]].pb(mp(R[i],i));
	solve3::solve();solve4::solve();
	for(int i=1;i<=qu;i++){
		if(!get<0>(solve3::ans[i])) printf("0\n");
		else if(!get<0>(solve4::ans[i]))
			printf("3\n%d %d %d\n",get<0>(solve3::ans[i]),get<1>(solve3::ans[i]),get<2>(solve3::ans[i]));
		else printf("4\n%d %d %d %d\n",get<0>(solve4::ans[i]),get<1>(solve4::ans[i]),get<2>(solve4::ans[i]),get<3>(solve4::ans[i]));
	}
	return 0;
}
posted @ 2021-06-21 13:21  tzc_wk  阅读(73)  评论(0)    收藏  举报