[NOI 2016] 优秀的拆分

题目传送-Luogu4117

题意:

\(T\)组数据,对于每组数据:
给你一个长度为\(n\)的字符串\(S\)
定义一个字符串\(t\)是好的,当且仅当它能被表示成\(aabb\)的形式,其中a和b都是字符串(可以相同)
\(S\)中有多少个子串是好的(本质相同位置不同也算不同)
\(T \le 10,n \le 30000\)

题解:

这题的构造方法极其巧妙,是道好题。
我们可以维护两个数组分别表示某个位置为结尾/开头的aa型字符串有多少个
那显然\(ans=\sum_{i=1}^{n-1}f_{i}*g_{i+1}\)(大概意思一下)
那考虑如何统计\(f\)\(g\)
我们枚举\(a\)的长度\(len\),并设置\(len,2*len,...,\lfloor \frac{n}{len}\rfloor *len\)为关键点,求出相邻关键点的\(LCP\)\(LCS\)(最长公共前/后缀),大概画幅图就知道对那些点有贡献,差分一下就可以得到\(f\)\(g\)了。

过程:

犯了个严重错误:见我的错题集LCA中的第1点
同时写代码的时候在变量代表的意义上纠结不清导致调题过程过长

代码:

const int N=30010;
int n;
char S[N];
int valf[N],valb[N];//i as end/start
int s_t[2][N<<2],t_s[2][N<<1],dep[2][N<<1],fa[2][N<<1],cur=0;
int head[N<<1],nxt[N<<1],to[N<<1],lst=0;
int NOW,ref[2][N];
inline void adde(int x,int y) {
    nxt[++lst]=head[x]; to[lst]=y; head[x]=lst;
}
namespace SAM {
    const int U=26;
    struct NODE {
        int tranc[U],dep,fa;
    }tre[N<<1];
    int las,rt,ind;
    inline int New_Node() {
        ++ind; mem(tre[ind].tranc,0); tre[ind].dep=tre[ind].fa=0;
        return ind;
    }
    inline void Init() {
        mem(tre,0);
        cur=lst=0; mem(head,0);
        ind=0; las=rt=New_Node();
    }
    inline void Insert(int x) {
        // printf("test:%d\n",tre[1].tranc[1]);
        int p=las,np=New_Node(); tre[np].dep=tre[p].dep+1;
        for(;p && !tre[p].tranc[x];p=tre[p].fa) tre[p].tranc[x]=np;
        if(!p) {tre[np].fa=rt;}
        else {
            int q=tre[p].tranc[x];
            if(tre[p].dep+1==tre[q].dep) {tre[np].fa=q;}
            else {
                int nq=New_Node(); tre[nq].dep=tre[p].dep+1;
                memcpy(tre[nq].tranc,tre[q].tranc,sizeof(tre[q].tranc));
                tre[nq].fa=tre[q].fa; tre[q].fa=tre[np].fa=nq;
                for(;p && tre[p].tranc[x]==q;p=tre[p].fa) tre[p].tranc[x]=nq;
            }
        }
        las=np;
    }
    inline void Build() {
        for(int i=1;i<=ind;i++)
            if(tre[i].fa) adde(tre[i].fa,i);
    }
    void dfs(int u) {
        // printf("u=%d dep=%d\n",u,tre[u].dep);
        dep[NOW][u]=tre[u].dep; fa[NOW][u]=tre[u].fa;
        s_t[NOW][++cur]=u; t_s[NOW][u]=cur;
        for(int i=head[u];i;i=nxt[i]) {
            int v=to[i];
            if(v) {
                dfs(v);
                s_t[NOW][++cur]=u;
            }
        }
    }
}
inline void Get_SAM(char *s) {
    if(NOW) reverse(s+1,s+n+1);
    for(int i=1;i<=n;i++) {
        SAM::Insert(s[i]-'a');
        ref[NOW][NOW ? n-i+1 : i]=SAM::las;
    }
    SAM::Build(); SAM::dfs(SAM::rt);
}
namespace ST {
    const int lgN=18;
    int st[2][N<<2][lgN+3],lg[N<<2];
    inline void Set() {
        lg[1]=0;
        for(int i=2;i<=120000;i++)
            lg[i]=lg[i>>1]+1;
    }
    inline void Init() {
        mem(st,63);
    }
    inline int Comp(int x,int y,int c) {return dep[c][x]<dep[c][y] ? x : y;}
    inline void Get_ST() {
        // printf("???:%d %d\n",cur,SAM::ind*2-1);
        assert(cur==SAM::ind*2-1);
        // for(int i=1;i<=SAM::ind*2-1;i++) printf("%d ",s_t[NOW][i]); puts("");
        for(int i=1;i<=SAM::ind*2-1;i++) st[NOW][i][0]=s_t[NOW][i];
        // printf("%d\n",tot);
        for(int j=1;j<=lgN;j++)
            for(int i=1;i+(1<<j)-1<=SAM::ind*2-1;i++) {
                st[NOW][i][j]=Comp(st[NOW][i][j-1],st[NOW][i+(1<<(j-1))][j-1],NOW);
                // printf("%d %d %d:%d\n",c,i,j,st[c][i][j]);
            }
        // printf("st[1][8][1]=%d\n",st[1][8][1]);
    }
    inline int Query(int c,int x,int y) {
        // printf("%d %d %d %d %d dep[2]=%d\n",c,x,y,ref[c][x],ref[c][y],dep[1][2]);
        x=t_s[c][ref[c][x]],y=t_s[c][ref[c][y]];
        if(x>y) swap(x,y);
        // if(x>y) swap(x,y);
        int tmp=lg[y-x+1];
        // printf("%d %d %d\n",x,y,tmp);
        // printf("ask::%d\n",fa[c][Comp(st[c][x][tmp],st[c][y-(1<<tmp)+1][tmp])]);
        return dep[c][Comp(st[c][x][tmp],st[c][y-(1<<tmp)+1][tmp],c)];
    }
}
inline void Init() {
    // puts("?");
    mem(dep,0);
    SAM::Init(); ST::Init();
    mem(valf,0); mem(valb,0);
}
inline void Work() {

    Init();
    NOW=0; Get_SAM(S); ST::Get_ST(); SAM::Init();
    NOW=1; Get_SAM(S); ST::Get_ST(); SAM::Init();
    // printf("1 2 :%d\n",ST::Query(1,1,2));
    for(int len=1;len<=n;len++) {
        for(int st=1;st+len<=n;st+=len) {
            int lcp=ST::Query(1,st,st+len),lcs=ST::Query(0,st,st+len);
            // printf("%d %d lcp=%d lcs=%d\n",st,st+len,lcp,lcs);
            if(lcp+lcs-2>=len-1) {
                int s=max(st,st+len-lcs),t=min(st+len-1,st+lcp-1);//s=st+len-1-lcs+1
                // printf("%d %d\n",s,t);
                // printf("%d %d %d\n",len,s+len,t+len+1);
                ++valf[s+len]; --valf[t+len+1];
                // printf("%d %d %d\n",len,s-len,t-len+1);
                ++valb[s-len+1]; --valb[t-len+2];
            }
        }
    }
    for(int i=1;i<=n;i++)
        valf[i]+=valf[i-1];
    for(int i=1;i<=n;i++)
        valb[i]+=valb[i-1];
    ll ans=0;
    for(int i=2;i<n-1;i++)
        ans+=1ll*valf[i]*valb[i+1];
    printf("%lld\n",ans);
}
signed main() {
    ST::Set();
    int T; read(T);
    while(T--) {
        scanf("%s",S+1); n=strlen(S+1);
        Work();
    }
    return 0;
}

用时:3h

转载于:https://www.cnblogs.com/functionendless/p/9562710.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值