题面: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;
}