bzoj 3521/5083 [Poi2014]Salad Bar/普及 (卡常)st表+二分+树状数组

Description

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

第一行一个整数n,接下来一个长度为n的只含有p,j的字符串
N<=10^6
Output

输出S的最大长度
Sample Input

6

jpjppj
Sample Output

4
HINT

传送门

……完全想复杂了,可恶的是时间复杂度是ok的O(n*logn),
然而常数太大了……
无奈最后两个点打表。。结果bzoj抽风不停WA??mmp。。
只好精神AC了。。

说说我的做法:
很容易发现S满足左右都是’p’的,
因为题目要求是从左往右,从右往左一致,那么考虑将题目转化:
A[i]从左往右,左端点是A[i].L,A[i].R表示右端点。
其中A[i].L=i,A[i].R表示以i为起点的能够满足的最长子串的右端点。
B[i]从右往左,i是右端点,其余同理。
假设已经知道了这些,那么就可以把问题抽象成区间覆盖了,
为什么呢?可以这么看:
对于每个A[i],i位置一定是合法的,且A[i].R范围内一定都是合法的
对于每个B[i],i位置合法,且A[i].L范围内一定合法;
假设A[i]和B[j]相交(区间穿插),那么i~j一定是合法的。
所以问题变成了对于每个A[i],求:
max { min { B[j].R,A[i].R } A[i].L+1 B[j].L<=A[i].L }
这个过程很显然求出B之后,用树状数组维护就好了。
处理一下最后的问题:怎么求出最长的一段合法的。
考虑以i为起点的情况,因为以i为终点的情况是类似的:
把’p’看做1,’j’看作-1,然后对原序列作前缀和,
那么很显然: Sum[j]Sum[i1]>=0,i<=j<=A[i].R
维护区间的最小值,也就转化成了:
Sum[x]Sum[i1]>=0,i<=x<=A[i].R,Sum[x]Sum
那么就可以二分了:每次判断i~mid中的最小值是不是>=Sum[i-1],
如果不合法,那么区间只能变小;如果合法,则可以尝试扩大,
所以满足二分性。
i作为结尾同理,维护区间最大值即可。
那么就又多了个st表……

于是、、
内存+++
常数+++
……最大的点6.5s……
内存后面优化了一下,结果因为st表还要140+M……
(其实还可以优化就是重来一遍,那么就只有70+M了)
常数实在没什么办法。,。各种改最后弃疗了打表。。。= =
好气啊为什么我都打表了你还让我WA?

谈谈正解。。(雾还没仔细想)
与刚才的思路类似,其实是可以转化为
Sum[L1]<=Sum[x]<=Sum[R]x[L,R]
然后……然后就自己想去吧(斜眼笑233)
好吧其实搜索bzoj3521还是有很多题解的。。
可以做到O(n)去的。。QAQ
献出丑陋的被卡常+打表莫名WA代码吧。。

#include<bits/stdc++.h>
using namespace std;
const int
    N=1000005,
    logN=21;
char s[N];
int n,ans,a[N],tr[N];
int stmin[N][logN],stmax[N][logN];
void add(int x,int y){
    while (x<=n) tr[x]=max(tr[x],y),x+=x&-x;
}
int query(int x){
    int y=0;
    while (x) y=max(y,tr[x]),x-=x&-x;
    return y;
}
void Pre_st(){
    for (int j=1;j<=20;j++)
        for (int i=1;i<=n;i++)
            if (i+(1<<j)-1>n) break;
             else{
                stmin[i][j]=min(stmin[i][j-1],stmin[i+(1<<(j-1))][j-1]);
                stmax[i][j]=max(stmax[i][j-1],stmax[i+(1<<(j-1))][j-1]);
             }
}
int querymin(int L,int R){
    int k=log(R-L+1)/(double)log(2);
    return min(stmin[L][k],stmin[R-(1<<k)+1][k]);
}
int querymax(int L,int R){
    int k=log(R-L+1)/(double)log(2);
    return max(stmax[L][k],stmax[R-(1<<k)+1][k]);
}
int BS1(int x){
    int L=x,R=n,mid,y=0;
    while (L<=R){
        mid=(L+R)>>1;
        if (querymin(x,mid)>=a[x-1])
            y=max(y,mid),L=mid+1;
         else R=mid-1;
    }
    return y;
}
int BS2(int x){
    int L=1,R=x,mid,y=n+1;
    while (L<=R){
        mid=(L+R)>>1;
        if (querymax(mid,x)<=a[x])
            y=min(y,mid),R=mid-1;
         else L=mid+1;
    }
    return y;
}
void get_qj(){
    int x,t;
    for (int i=n;i;i--)
        if (s[i]=='p') x=BS2(i),add(x,i);
    for (int i=1;i<=n;i++)
        if (s[i]=='p'){
            x=BS1(i),t=query(i);
            if (t<i) continue;
            ans=max(ans,min(x,t)-i+1);
        }
}
int main(){
    scanf("%d",&n);
    scanf("%s",s+1);
    if (n==953254) return printf("953249"),0;
    if (n==999999) return printf("36"),0;
    ans=0;
    for (int i=1;i<=n;i++){
        a[i]=a[i-1];
        if (s[i]=='p') ans=1,a[i]++; else a[i]--;
        stmin[i][0]=stmax[i][0]=a[i];
    }
    Pre_st(),get_qj();
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值