【HZOI】 排队

Description

一个队伍中站在最前面的人是最矮的,并且站在最后面的人是最高的,那么这个队伍是和谐的。 
现在 N个人站成一队,第i个人身高为 ℎi,对于队伍中的一个区间[L,R],如果第 L 个人的高度小于[L+1,R]所有人的高度,并 且第R个人的高度大于[L,R−1]所有人的高度,那么[L,R]称 为一个和谐区间。 
现在给出队伍的情况,求最长的和谐区间的长度。

Input

第一行,一个数N,接下来N行,每行一个数 ℎi

Output

一个数,表示最长和谐区间的长度

Sample Input

5
1
2
3
4
1

Sample Output

4

Hint

N≤100000,1≤ℎi≤2^31


【分析】

说说我的思路

1.先缩点。

    就是说把一坨一样的缩成一个。为什么呢,因为方便,而且会快些。

    正确性:考虑这样一组数据N为10:1 1 1 2 2 1 1 7 9 9

           答案是完全等价:1 2 17 9

           所以缩点是没问题的

2.用单调队列(这里应该是单调栈)来算出每个位置右边第一个小于等于他的数的位置。然后r[i]就存那个位置。

3.对于每个i求出i+1~r[i]-1的最大值出现在哪个地方,设为loc[i];

最后答案就是max(loc[i]+1-i)(1<=i<=N)

 

注意细节的处理,第2步最后要让0进栈,才能把所有元素出栈,(我写的严格单调递增的单调栈);

至于第3步,RMQ问题,随便线段树啊,ST啊就搞掉了。当然对于查询次数为10w级,还是推荐用ST节约时间(空间当然会增加)。


【代码】

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<iostream>
#include<algorithm> 
using namespace std;
int N,v[100005],Q[100005],Qr[100005],r[100005];
int Fmax[100005][20],Fr[100005][20];
void _in(int &x)
{
	char t=getchar();
	while(t<'0'||'9'<t) t=getchar();
	for(x=t-'0',t=getchar();'0'<=t&&t<='9';x=x*10+t-'0',t=getchar());
}
void _init()
{
	_in(N);
	int j=1;
	for(int i=1;i<=N;i++,j++)
	{
	    _in(v[j]);
	    if(v[j]==v[j-1])
	    	j--;
	}
	N=--j;
	v[N+1]=0;
	for(int i=1;i<=N;i++)
	{
	    Fmax[i][0]=v[i];
	    Fr[i][0]=i;
	}
}
void _solve()
{
	int head=1,tail=2;
	Q[1]=v[1];
	Qr[1]=1;
	for(int i=2;i<=N+1;i++)
	{
		while(v[i]<=Q[tail-1]&&tail>head)
		{
			tail--;
			r[Qr[tail]]=i-1;
		}
		Qr[tail]=i;
		Q[tail++]=v[i];
	}
	for(int j=1;j<=floor(log(N)/log(2));j++)
	    for(int i=1;i<=(N+1-(1<<j));i++)
	    {
	    	Fmax[i][j]=max(Fmax[i][j-1],Fmax[i+(1<<(j-1))][j-1]);
	    	if(Fmax[i][j]==Fmax[i][j-1])
	    		Fr[i][j]=Fr[i][j-1];
	    	else
	    		Fr[i][j]=Fr[i+(1<<(j-1))][j-1];
	    }
	int a,b,k,ans=0,Qmax,Qr;
	for(int i=1;i<=N;i++)
	{
		a=i+1;
		b=r[i];
		if(a>=b) continue;
		k=floor(log(b+1-a)/log(2));
		Qmax=max(Fmax[a][k],Fmax[b+1-(1<<k)][k]);
		if(Qmax==Fmax[a][k])
			Qr=Fr[a][k];
		else
			Qr=Fr[b+1-(1<<k)][k];
		ans=max(ans,Qr+1-i);
	}
	printf("%d\n",ans);
}
int main()
{
	_init();
	_solve();
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值