[POI2014]Salad Bar

时间:2023-03-09 16:25:42
[POI2014]Salad Bar

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

思路:
  令$p$为$1$,$j$为$-1$。用$sum[i]$表示$1\sim i$的前缀和,则题目所求相当于找到一个最长的区间$[l,r]$,满足$\forall i\in[l,r],sum[l-1]\le sum[i]\le sum[r]$。
  这也就意味着对于一个满足条件的区间$[l,r]$,$sum[l-1]$为最小值,$sum[r]$为最大值。
  预处理每一个位置$i$能扩展到的最左端点$left[i]$和最右端点$right[i]$。答案相当于找到一个区间$[l,r]$使得$left[r]\le l,right[l]\ge r$。将点按照$right[i]$排序,枚举对应的$i$作为$l$。用树状数组在每个$left[i]$对应的位置上维护最大的$i$。右端点$r$就是当前加入线段树且$left[i]\le l$的点中,对应的$i$的最大值。

 #include<cstdio>
#include<cctype>
#include<climits>
#include<algorithm>
inline int getint() {
register char ch;
while(!isdigit(ch=getchar()));
register int x=ch^'';
while(isdigit(ch=getchar())) x=(((x<<)+x)<<)+(ch^'');
return x;
}
inline char getalpha() {
register char ch;
while(!isalpha(ch=getchar()));
return ch;
}
const int N=1e6+;
int n,a[N],pos[N*],left[N],right[N],seq[N];
inline bool cmp(const int &a,const int &b) {
return right[a]<right[b];
}
class FenwickTree {
private:
int val[N];
int lowbit(const int &x) const {
return x&-x;
}
public:
void modify(int p,const int &x) {
for(;p<=n;p+=lowbit(p)) {
val[p]=std::max(val[p],x);
}
}
int query(int p) const {
int ret=;
for(;p;p-=lowbit(p)) {
ret=std::max(ret,val[p]);
}
return ret;
}
};
FenwickTree t;
int main() {
n=getint();
for(register int i=;i<=n;i++) {
seq[i]=i;
a[i]=getalpha()=='p'?:-;
}
std::fill(&pos[],&pos[N*],-);
for(register int i=,sum=a[];i<=n;sum+=a[++i]) {
left[i]=pos[sum+N+]+;
pos[sum+N]=i;
}
std::fill(&pos[],&pos[N*],n+);
for(register int i=n,sum=a[n];i>=;sum+=a[--i]) {
right[i]=pos[sum+N+]-;
pos[sum+N]=i;
}
int ans=;
std::sort(&seq[],&seq[n]+,cmp);
for(register int i=,j=;i<=n;i++) {
const int l=seq[i];
if(a[l]==-) continue;
for(;j<=right[l];j++) t.modify(left[j],j);
const int r=t.query(l);
ans=std::max(ans,r-l+);
}
printf("%d\n",ans);
return ;
}