【BZOJ5083】普及 单调栈+二分+RMQ

【BZOJ5083】普及

Description

有一个长度为n的字符串,每一位只会是p或j。你需要取出一个子串S(从左到右或从右到左一个一个取出),使得
不管是从左往右还是从右往左取,都保证每时每刻已取出的p的个数不小于j的个数。你需要最大化|S|。

Input

第一行一个整数n,接下来一个长度为n的只含有p,j的字符串
N<=10^6

Output

输出S的最大长度

Sample Input

6
jpjppj

Sample Output

4

题解:我们将区间和看成前缀相减和后缀相减,记前缀和为s1[i],后缀和为s2[i],那么区间[l,r]是合法的就等价于s1[r]>=s1[l-1...r-1],s2[l]>=s2[l+1...r+1]。我们可以用单调栈维护每个数左边第一个s1比它大的ls,和右边第一个s2比它大的rs。那么询问就变成了求最大的r-l,满足l>=ls[r]且r<=rs[l]。我们枚举l,然后用RMQ维护区间ls的最小值,然后二分查找即可。

Claris是怎么跑得那么快的啊~

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn=1000010;
int n,top,ans;
int ls[maxn],rs[maxn],v[maxn],s1[maxn],s2[maxn],st[maxn],mn[20][maxn],Log[maxn];
char str[maxn];
inline int query(int a,int b)
{
	int k=Log[b-a+1];
	return min(mn[k][a],mn[k][b-(1<<k)+1]);
}
int main()
{
	scanf("%d%s",&n,str+1);
	int i,j,l,r,mid;
	for(i=1;i<=n;i++)	s1[i]=s1[i-1]+(str[i]=='p'?1:-1);
	for(i=n;i>=1;i--)	s2[i]=s2[i+1]+(str[i]=='p'?1:-1);
	for(st[top=1]=0,i=1;i<=n;i++)
	{
		while(top&&s1[st[top]]<=s1[i])	top--;
		mn[0][i]=ls[i]=!top?1:(st[top]+2),st[++top]=i;
	}
	for(st[top=1]=n+1,i=n;i>=1;i--)
	{
		while(top&&s2[st[top]]<=s2[i])	top--;
		rs[i]=!top?n:(st[top]-2),st[++top]=i;
	}
	for(i=2;i<=n;i++)	Log[i]=Log[i>>1]+1;
	for(j=1;(1<<j)<=n;j++)	for(i=1;i+(1<<j)-1<=n;i++)	mn[j][i]=min(mn[j-1][i],mn[j-1][i+(1<<(j-1))]);
	for(i=1;i<=n;i++)
	{
		l=i-1,r=rs[i];
		while(l<r)
		{
			mid=(l+r)>>1;
			if(query(mid+1,r)<=i)	l=mid+1;
			else	r=mid;
		}
		ans=max(ans,r-i+1);
	}
	printf("%d",ans);
	return 0;
}
posted @ 2017-10-29 10:56  CQzhangyu  阅读(456)  评论(0编辑  收藏  举报