【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
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; }
| 欢迎来原网站坐坐! >原文链接<