BZOJ3521[Poi2014] Salad Bar

30 篇文章 0 订阅
17 篇文章 0 订阅

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[L1]0,sum[R]sum[k1]0

即:
k[L,R],sum[L1]sum[k]sum[R]

这意味着什么呢,左端点的 sum 值是这个区间最小的,右端点的 sum 值是这个区间最大的。而我们可以处理出 L[i] 表示 i 左侧第一个大于sum[i] sum 值的位置(即以 i 作为右端点左端点最多能延伸至的位置),R[i]类似。

于是我们的问题就变成了:

MaximizeRL+1s.t.L[R]L,RR[L]

这就是一个经典的二维问题了,可以逐渐降维解决:

R[j] 从小到大排序,依次把 iR[j] i 加入候选部分,然后再用BIT维护剩下的L数组,就可以解决了,复杂度 O(nlogn)

顺便一句,上面对于 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值