BZOJ3521[Poi2014] Salad Bar
Description
有一个长度为n的字符串,每一位只会是p或j。你需要取出一个子串S(从左到右或从右到左一个一个取出),使得不管是从左往右还是从右往左取,都保证每时每刻已取出的p的个数不小于j的个数。你需要最大化|S|。
Input
第一行一个数n,第二行一个长度n的字符串。
Output
S的最大长度。
Sample Input
6
jpjppj
Sample Output
4
HINT
【样例解释】
取pjpp这个串。
【数据范围】
n≤1000000
Solution:
这道题的难点在于如何抽象这个题目所给的条件。
对于答案区间
[L,R]
,有:
∀k∈[L,R],sum[k]−sum[L−1]≥0,sum[R]−sum[k−1]≥0
即:
∀k∈[L,R],sum[L−1]≤sum[k]≤sum[R]
这意味着什么呢,左端点的 sum 值是这个区间最小的,右端点的 sum 值是这个区间最大的。而我们可以处理出 L[i] 表示 i 左侧第一个大于
于是我们的问题就变成了:
MaximizeR−L+1s.t.L[R]≤L,R≤R[L]
这就是一个经典的二维问题了,可以逐渐降维解决:
对
R[j]
从小到大排序,依次把
i≤R[j]
的
i
加入候选部分,然后再用BIT维护剩下的
顺便一句,上面对于 L,R 数组的处理复杂度为 O(nlogn) ,但是由于本题特殊的性质:前缀和连续变化,因此直接记录上一个值的位置就可以了,可以做到 O(n) 。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#define M 1000005
#define Max 1000001
using namespace std;
struct Node{
int R,id;
bool operator <(const Node &a)const{
return R<a.R;
}
}A[M];
int sum[M],L[M],val[M],pos[M*2],n;
char str[M];
inline int lowbit(int x){return x&(-x);}
void Add(int x,int v){
while(x<=n){
if(v>val[x])val[x]=v;
x+=lowbit(x);
}
}
int Query(int x){
int rs=0;
while(x){
if(val[x]>rs)rs=val[x];
x-=lowbit(x);
}
return rs;
}
int main(){
scanf("%d",&n);
scanf("%s",str+1);
for(int i=1;i<=n;i++){
sum[i]=(str[i]=='p')?1:-1;
sum[i]+=sum[i-1];
}
for(int i=0;i<=Max*2;i++)pos[i]=n;
for(int i=n-1;i>=0;i--){
A[i].R=pos[sum[i]+Max-1];
A[i].id=i+1;
pos[sum[i]+Max]=i;
}
for(int i=0;i<=Max*2;i++)pos[i]=1;pos[Max]=0;
for(int i=1;i<=n;i++){
L[i]=pos[sum[i]+Max+1];
pos[sum[i]+Max]=i;
}
sort(A,A+n);
int now=1,ans=0;
for(int i=0;i<n;i++){
while(now<=A[i].R){
Add(L[now]+1,now);
now++;
}
int rs=Query(A[i].id+1);
if(rs-A[i].id+1>ans)ans=rs-A[i].id+1;
}
printf("%d\n",ans);
return 0;
}