BZOJ4384 POI2015 Trzy wieże


Description

给定一个长度为n的仅包含 B C S 三种字符的字符串,请找到最长的一段连续子串,使得这一段要么只有一种字符,要么有多种字符,但是没有任意两种字符出现次数相同。

Input

第一行包含一个正整数n(1<=n<=1000000),表示字符串的长度。

第二行一个长度为n的字符串。

Output

包含一行一个正整数,即最长的满足条件的子串的长度。

Sample Input

9
CBBSSBCSC

Sample Output

6

Input Details

选择BSSBCS这个子串。


一开始看到此题的时候非常自然地想到USACO_2014_Open_Sliver的一道题目。若只有两种字符,那么我们可以利用前缀和的思想,将每个字符分别表示作±1,这样通过前缀和的累加,对于当前位置,只需要在前面的前缀和中找到位置最小的,与当前值不等的前缀和转移即可,时间复杂度 O(nlogn)

但是这题的情况不一样。它要求 [L,R] 内的元素两两不等,故不能采用前文的做法(但是前缀和的思想依旧适用)。并且由于这个原因,实际上这个问题如果单从三个元素上考虑的话就是三维的问题了。

于是按照NOIP的要求,咱不能放弃暴力啊!暴力的复杂度是 O(n2) ,显然是过不掉的。

最后毫无办法,于是只能从玄学角度入手:

首先,如果采用一般的前缀和,我们就需要判断以下几条式子:

sumB[i]sumB[j]sumC[i]sumC[j]

sumC[i]sumC[j]sumS[i]sumS[j]

sumS[i]sumS[j]sumB[i]sumB[j]

才能得出i可以从j转移的结论,显然此时的比较很困难。最方便的比较就是将上述的前缀和分别转化成3个属性,或者说三个固定的数,使得比较的时候只需要判断i,j的三个属性是否对应不相等即可。通过对上述式子的变形,我们可以得到下述内容:

sumB[i]sumC[i]sumB[j]sumC[j]
sumC[i]sumS[i]sumC[j]sumS[j]
sumS[i]sumB[i]sumS[j]sumB[j]

同侧的内容就是当前位置的三个属性。

在这个基础上,我们解决多维问题,首先是要对其降维

我们考虑将所有相同的 a=sumB[i]sumC[i] 一起查询与更新,即不让两两之间的元素产生更新,这样a不重复的问题就解决了,复杂度 O(n)O(nlogn)

接下来还要再降一维才好操作。由于复杂度的问题,我们必然会想到采用数据结构去优化上述关系。所以我们考虑将 b=sumC[i]sumS[i] 放进数据结构的下标内。那么只要在数据结构的其他节点内,找到可以更新的( c=sumS[i]sumB[i] 不相等)pos,使得abs(pos-i)尽可能大即可。

为了 O(1) 查询某个节点内可能的最值,我们需要在每个节点上维护最大、次大值,最小、次小值(维护次值的原因是避免最值的c与当前pos的c冲突)。

本题还是一道考验代码功底的题目,代码实现比较困难。(→_→)

当然,具体实现请看代码。我们需要分别维护[1,pos-1]和[pos+1,end]之间的情况,所以代码中分别实现了前后缀树状数组。Claris大犇的做法是将b值颠倒,使得所有的后缀会变成前缀来更新。

#include <bits/stdc++.h>
#define clear(x,val) memset(x,val,sizeof(x))
using namespace std;
static const int M=1000005;
int n,cnt[3];
char str[M];
struct node{int a,b,c;}Node[M];
int head[M<<1],nxt[M];
void add_edge(int val,int v){nxt[v]=head[val],head[val]=v;}
struct Binary_tree{
    struct node{
        int mx1,mx2,mi1,mi2;
        node(){mx1=mx2=mi1=mi2=-1;}
        void up(int val,int pos){
            if(!~mi1){mi1=mx1=pos;return;}
            if(mi1>pos){
                if(Node[mi1].c!=val)mi2=mi1; mi1=pos;
            }else if((!~mi2||mi2>pos)&&Node[mi1].c!=val)mi2=pos;
            if(mx1<pos){
                if(Node[mx1].c!=val)mx2=mx1; mx1=pos;
            }else if((!~mx2||mx2<pos)&&Node[mx1].c!=val)mx2=pos;
        }//此处的更新实现比较复杂
    }bit[M<<1];
    int ask(node t,int pos){
        int ans=0;
        if(~t.mx1&&Node[t.mx1].c!=Node[pos].c){
            if(ans<t.mx1-pos)ans=t.mx1-pos;
        }else if(~t.mx2&&Node[t.mx2].c!=Node[pos].c){
            if(ans<t.mx2-pos)ans=t.mx2-pos;
        }
        if(~t.mi1&&Node[t.mi1].c!=Node[pos].c){
            if(ans<pos-t.mi1)ans=pos-t.mi1;
        }else if(~t.mi2&&Node[t.mi2].c!=Node[pos].c){
            if(ans<pos-t.mi2)ans=pos-t.mi2;
        }
        return ans;
    }
    #define lowbit(x) x&-x
    void pre_insert(int w,int pos){
        while(w<M*2){
            bit[w].up(Node[pos].c,pos);
            w+=lowbit(w);
        }
    }
    void suf_insert(int w,int pos){
        while(w){
            bit[w].up(Node[pos].c,pos);
            w-=lowbit(w);
        }
    }
    int pre_query(int w,int pos){
        int ans=0;
        while(w){
            ans=max(ans,ask(bit[w],pos));
            w-=lowbit(w);
        }return ans;
    }
    int suf_query(int w,int pos){
        int ans=0;
        while(w<2*M){
            ans=max(ans,ask(bit[w],pos));
            w+=lowbit(w);
        }return ans;
    }
}pre,suf;
int main(){
    scanf("%d%s",&n,str+1);
    clear(head,-1),clear(nxt,-1);
    Node[0]=(node){0,0,0};
    add_edge(Node[0].a+M,0);
    for(int i=1;i<=n;i++){
        switch(str[i]){
            case 'B':++cnt[0];break;
            case 'C':++cnt[1];break;
            case 'S':++cnt[2];break;
        }
        Node[i]=(node){cnt[0]-cnt[1],cnt[1]-cnt[2],cnt[2]-cnt[0]};
        add_edge(Node[i].a+M,i);
    }
    int ans=0;
    for(int i=1;i<M*2;i++){
        for(int j=head[i];~j;j=nxt[j]){
            int v1=pre.pre_query(Node[j].b+M-1,j),
                v2=suf.suf_query(Node[j].b+M+1,j);
            if(ans<v1)ans=v1;
            if(ans<v2)ans=v2;
        }
        for(int j=head[i];~j;j=nxt[j]){
            pre.pre_insert(Node[j].b+M,j);
            suf.suf_insert(Node[j].b+M,j);
        }
    }
    int pre=0;
    for(int i=1;i<=n+1;i++)
        if(str[pre]!=str[i]){
            ans=max(ans,i-pre);
            pre=i;
        }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值