CF Global Round 21 D/CF1696D Permutation Graph (单调栈,线段树)

题面:https://codeforces.com/contest/1696/problem/D

解题思路:

题目说是要建图,刚开始我想的是线段树建图,差点就放弃了(忘记怎么敲了)

然后就发现其实和之前一道,问是否能满足每一个区间和都大于0的题目挺像的,所以不用建图了。

因为是从左往右跳,首先我们知道以now为左端点,右端点可以是啥情况。

  • 左端点区间最小,右端点区间最大,那么这个右端点能取到的最右边应该是:右边第一个比a[now]大的坐标-1;
  • 左端点区间最大,右端点区间最小,右端点范围同上。

所以得先用单调栈,把每个的右边第一个大(小)的位置求出来,记为lmn[ ],lmx[ ]。

找到了范围,下一步就是找这个区间内可以跳的点。

假设左端点now是最大值,且我们知道 i , j 可以跳,(i<j,且i,j是相邻两个以now为最大值跳的点),我们可以推出:

  • a[i]>a[j]

    否者a[j]不是区间最小值

  • i能跳的超过j的点,j都能跳

    因为 i 跳的想超过 j,那么以 i 为左端点必须是最大值,最小值的话 j 会比他小,不可以,假设 i 能跳到 k ,那么那么这个区间最大值是i,最小值是k,而考虑[j,k]区间也完全满足

所以我们按最远的跳就行,最远的就是这个区间内的最小值的坐标。

这里有个问题。

  • 为啥不能往回跳?

    假设i最大,往回跳到k,k为区间最小。那么以k最小值的能跳的超过i的点,i都行。(k不能为最大值)

  • 为啥在 i , j之间我不能先调整将i调整到区间内其他点再跳?

    我们知道a[i]>a[j],且相邻,所以中间的值都比a[i]大,否则这两个不会相邻。那么只能以a[i]为最小值,跳到a[k],剩下步骤推理同上。

所以就是按黄色走法,大小轮流换,贪心走下去就行。

代码实现:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <stack>
#define DEBUG(x) cout<<"** "<<x<<" **"<<endl;
#define ls (x<<1)
#define rs (x<<1|1)
#define N 250005
using namespace std;
typedef long long ll;

int a[N];
int mn[N*4],mx[N*4];//线段树
int m[N];//m[i]表示i值所在的坐标
int rmx[N],rmn[N];//右边第一个比他大(小)的坐标
void build(int l,int r,int x){
	if(l==r){
		mn[x]=mx[x]=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,ls);
	build(mid+1,r,rs);
	mn[x]=min(mn[ls],mn[rs]);
	mx[x]=max(mx[ls],mx[rs]);
}
int querymx(int A,int B,int l,int r,int x){
	if(A<=l&&B>=r){
		return mx[x];
	}
	int mid=(l+r)>>1;
	int ret=-1;
	if(A<=mid){
		ret=max(ret,querymx(A,B,l,mid,ls));
	}
	if(B>=mid+1){
		ret=max(ret,querymx(A,B,mid+1,r,rs));
	}
	return ret;
}
int querymn(int A,int B,int l,int r,int x){
	if(A<=l&&B>=r){
		return mn[x];
	}
	int mid=(l+r)>>1;
	int ret=1e9+7;
	if(A<=mid){
		ret=min(ret,querymn(A,B,l,mid,ls));
	}
	if(B>=mid+1){
		ret=min(ret,querymn(A,B,mid+1,r,rs));
	}
	return ret;
}
int main(){
	int t=0;
	cin>>t;
	while(t--){
		int n;
		cin>>n;
		stack<int> st;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			m[a[i]]=i;
		}
		build(1,n,1);
		for(int i=1;i<=n;i++){
			while(!st.empty()&&a[st.top()]<a[i]){
				rmx[st.top()]=i;
				st.pop();
			}
			st.push(i);
		}
		while(!st.empty()){
			rmx[st.top()]=n+1;
			st.pop();
		}
		for(int i=1;i<=n;i++){
			while(!st.empty()&&a[st.top()]>a[i]){
				rmn[st.top()]=i;
				st.pop();
			}
			st.push(i);
		}
		while(!st.empty()){
			rmn[st.top()]=n+1;
			st.pop();
		}
		int now=1;
		int sum=0;
		while(now<n){
			sum++;
			if(a[now]<a[now+1]){
				now=m[querymx(now,rmn[now]-1,1,n,1)];
			}else{
				now=m[querymn(now,rmx[now]-1,1,n,1)];
			}
		}
		cout<<sum<<endl;
	}
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值