P9290 [ROI 2018] Decryption 题解
题目大意
给定一个长度为 \(\large n\) 的序列 \(\large a\),对于 \(\large a\) 的一个子段 \(\large[a_l,a_{l+1},...,a_r]\),若 \(\large a_l=\min\limits_{i=l}^{r}(a_i), a_r=\max\limits_{i=l}^{r}(a_i)\),则这个子段是合法的,问如何划分才能使合法的子段数最小,输出最小子段数。
思路
首先,我们预处理出 \(\large a_i\) 右侧第一个小于它的数的位置 \(\large mn_i\),和 \(\large a_i\) 右侧第一个大于等于它的数 \(mx_i\),可以用单调栈预处理出来。
对于一个子段 \(\large[l,r]\),若 \(\large mx_r<mn_l\),则使 \(\large r=mx_r\),直到不满足此条件。
这么做的道理很简单。要想使合法子段数最小,显然要使子段长度最长。对于一个左端点 \(\large l\),它所对应的能使子段长度最长的右端点 \(\large l\) 一定是在区间 \(\large [l,mn_l)\) 中的最大值的位置,如果最大值在这段区间中出现了多次,则取最靠右的位置。
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#define reg register
#define int long long
using namespace std;
inline int read()
{
short f=1;
int x=0;
char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,a[300010],mx[300010],mn[300010],ans,nxt[300010],pre[300010];
stack<int>maxx,minn;
signed main()
{
n=read();
for(reg int i=1;i<=n;i=-~i) a[i]=read();
maxx.push(1);minn.push(1);
for(reg int i=2;i<=n;i=-~i)
{
while(!maxx.empty()&&a[i]>=/*大于等于,因为会有相同的值,赛时没写等号挂了*/a[maxx.top()]) mx[maxx.top()]=i,maxx.pop();
while(!minn.empty()&&a[i]<a[minn.top()]) mn[minn.top()]=i,minn.pop();
maxx.push(i);minn.push(i);
}
while(!maxx.empty()) mx[maxx.top()]=n+1,maxx.pop();
while(!minn.empty()) mn[minn.top()]=n+1,minn.pop();
for(reg int l=1,r=1;r<=n;r=-~r)
{
while(mx[r]<mn[l]&&r!=mx[r]&&r<=n) r=mx[r];
ans=-~ans;l=-~r;
}
return printf("%lld",ans),0;
}

赛时挂了,,,
浙公网安备 33010602011771号