Loj#2430. 「POI2014」沙拉餐厅 Salad Bar
感慨……大概有将近一个月没写博客了……
代码+题解
当然,如果思路不够清晰的话也可以选择O(nlogn)的解法。
通过其他数据结构或算法例如:RMQ,来实现。
其实真正转化成那个优美的式子以后,可以发现实际上就是造前缀和,再求每个点 i 前面第一个比它位置 x。求出来后再求 x 到 i 中的最小值的位置 L,i - L 即为一组可行解。
/*
s[i]-s[L-1]>=0
s[i]>=s[L-1]
(这里本应为(s[n]-s[i-1]),但由于对于j=i+1的位置上也要符合,
把这个j代入式子得到下式: )
(s[n]-s[i])-(s[n]-s[R])>=0
s[R]>=s[i]
思路分析:
我们可以推导出满足条件的L,R一定满足s[L-1]<=s[i]<=s[R],其中i∈[L,R]
由此,对于一个节点i能往左推到最远的位置就是(前缀和):
设向左推第一个比他大的点的位置为x
从x到i之间最左边的最小值的位置le,
那么答案=i-le。
我们要做的就是快速求出这个点的位置。
可以用单调栈求出每个点前面第一个比他大的数,
从单调栈每个节点开始的最小值也是可以顺带求出的。
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+5;
int n,a[maxn],st[maxn],f[maxn],top,ans;
//a是前缀和;st是单调栈;f[j],表示从j到当前位置的(最小值的位置);
int _min(int x,int y){return a[f[x]]>a[f[y]]?y:x;}
int main()
{
scanf("%d",&n);
char c=getchar();
while (c!='j'&&c!='p') c=getchar();a[1]=(c=='p'?1:-1);
for (int i=2;i<=n;++i) c=getchar(),a[i]=(c=='p'?1:-1)+a[i-1];
top=ans=0;
for (int i=1;i<=n;++i)
{
st[++top]=i;f[i]=i;int id=i;
while (top>1&&a[st[top]]>=a[st[top-1]])
{
int lst=st[top-1],now=st[top];
id=_min(lst,id);//这个地方id表示的是最小值在id到i这段区间里,并非实际位置。实际位置为f[id]
st[--top]=now;
}
f[st[top-1]]=f[_min(st[top-1],id)];//这就是为什么这里要套在f[ ]里面
if (top>1||a[i]>=0) ans=max(ans,i-f[st[top-1]]);
}
printf("%d",ans);
return 0;
}