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;
}
posted @ 2023-10-13 15:58  Sorato  阅读(52)  评论(0)    收藏  举报