BZOJ3521: [Poi2014]Salad Bar

题目大意:有一个长度为n的字符串,每一位只会是p或j。求一个最长子串,使得不管是从左往右还是从右往左取,都保证每时每刻已取出的p的个数不小于j的个数。


首先先扫一遍得到以每个点为左端点,只考虑从左向右取,最远能取到哪,并标记每一个点最早被哪一个点扫到

然后再从后往前扫一遍,开两个栈,一个记录当前仍符合条件的右端点,一个记录已经被扫过的左端点,每当有元素从第一个栈里弹出时,就在第二个栈里二分找出符合两个条件的最远的左端点


#include<iostream>
#include<cstdio>
#define N 1000010
using namespace std;
char c[N];
int pre[N];
int s[N],t;
int fir[N],far[N];
int b[N],p;
int main()
{
	int n;
	scanf("%d",&n);
	int i,j;
	scanf("%s",c+1);
	for(i=1;i<=n;i++)
	{
		if(c[i]=='p')
		pre[i]=pre[i-1]+1;
		else pre[i]=pre[i-1]-1;
	}
	for(i=1;i<=n;i++)
	{
		while(t&&pre[i]-pre[s[t]-1]<0)
		{
			far[s[t]]=i-1;
			t--;
		}
		if(t) fir[i]=s[1];
		else if(pre[i]-pre[i-1]==1) fir[i]=i;
		else fir[i]=707185547;
		if(pre[i]-pre[i-1]==1) t++,s[t]=i;
	}
	while(t)
	{
		far[s[t]]=n;
		t--;
	}
	int ans=0;
	int L,R,mid;
	for(i=n;i>=1;i--)
	{
		while(t&&pre[s[t]]-pre[i-1]<0)
		{
			L=2;R=p+1;
			while(L<R)
			{
				mid=(L+R)>>1;
				if(far[b[mid]]>=s[t]) L=mid+1;
				else R=mid;
			}
			ans=max(ans,s[t]-max(fir[s[t]]-1,b[L-1]-1));
			t--;
		}
		if(pre[i]-pre[i-1]==1)
		{
			t++;s[t]=i;
			while(p&&far[b[p]]<=far[i]) p--;
			p++;b[p]=i;
		}
	}
	while(t)
	{		
		L=2;R=p+1;
		while(L<R)
		{
			mid=(L+R)>>1;
			if(far[b[mid]]>=s[t]) L=mid+1;
			else R=mid;
		}
		ans=max(ans,s[t]-max(fir[s[t]]-1,b[L-1]-1));
		t--;
	}
	printf("%d",ans);
}

你可能感兴趣的:(poi,栈,单调栈,单调队列,bzoj)