2019牛客第四场I题 string

该博客讨论了如何求解一个字符串中本质不同的非回文子串个数。通过建立广义后缀自动机,可以计算出字符串及其逆序字符串的所有子串,然后排除回文串和相同但非回文的串,最终得到所需答案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接:https://ac.nowcoder.com/acm/contest/884/I

 

题意:求string串有多少个本质不同的子串,且这些子串之间两两不存在 a==rev(a),即不存在长度1以上的回文串

题解:

要算string 和 rev(string)的所有子串,对string

和rev(string)建立广义后缀自动机,则理论上所有子串增加了一倍,但实际回文串和 不是回文但a == rev(a)的串没有增加,

比如 aba子串ab和ba。则我们计算出此时不同串个数 ans1, 再计算出string串中回文串数 ans2,则(ans1+ans2)/2即为所求.

 

#include<bits/stdc++.h>
 
using namespace std;
typedef long long LL;
 
const int maxn = 2*1e6+5;
const int maxc = 28;
 
char str[maxn];
 
struct SMA{
    int len[maxn * 2];
    int link[maxn * 2];
    int cnt[maxn * 2];
    int nxt[maxn * 2][maxc];
    int idx;
    int last;
    LL epos[maxn * 2];
    LL a[maxn];
 
    void init(){
        last = idx = 1;
        link[1] = len[1] = 0;
    }
 
    //SMA建图
    void SMA_Extend(int c){
        int x = ++idx;
        len[x] = len[last] + 1;
        epos[x] = 1;
        int p;
 
        for(p = last; p && !nxt[p][c]; p = link[p]) nxt[p][c] = x;
 
        if(!p) link[x] = 1, cnt[1]++;
        else{
            int q = nxt[p][c];           //p通过c转移到的节点
            if(len[p] + 1 == len[q])     //pq是连续的
                link[x] = q, cnt[q]++;   //将新节点后缀连接指向q即可,q节点的被后缀连接数+1
            else{
                int nq = ++idx;          //不连接,需要复制一份q节点
                len[nq] = len[p] + 1;    //令nq与p连接
                link[nq] = link[q];      //因后面link[q]改变此处不加cnt
                memcpy(nxt[nq], nxt[q], sizeof(nxt[q]));    //复制q的信息给nq
                for(; p && nxt[p][c] == q; p = link[p])
                    nxt[p][c] = nq;   //沿着后缀连接,将所有通过c转移为q
 
                link[q] = link[x] = nq;   //将x和q后缀连接改为nq
                cnt[nq] += 2;  //nq增加两个后缀连接
            }
        }
        last = x;
    }
 
    //求不相同子串数量
    LL getSubNum(){
        LL ans = 0;
        for(int i = 2; i <= idx; i++) ans += len[i] - len[link[i]];
        return ans;
    }
}sma;
 
struct PAM{
    int nxt[maxn][maxc];
    int fail[maxn];
    int cnt[maxn];
    int len[maxn];
    int S[maxn];
    int last, n, p;
 
    int Create(int rt){
        for(int i = 0; i <= 26; i++) nxt[p][i] = 0;
        cnt[p] = 0;
        len[p] = rt;
        return p++;
    }
 
    void init(){
        p = last = n = 0;
        Create(0);
        Create(-1);
        S[n] = -1;
        fail[0] = 1;
    }
 
    int getFail(int x){
        while(S[n-len[x]-1] != S[n]) x = fail[x];
        return x;
    }
 
    void Insert(int c){
        S[++n] = c;
        int cur = getFail(last);
        if(!nxt[cur][c]){
            int now = Create(len[cur] + 2);
            fail[now] = nxt[getFail(fail[cur])][c];
            nxt[cur][c] = now;
            //num[now] = num[fail[now]] + 1;
        }
        last = nxt[cur][c];
        cnt[last]++;
    }
}pam;
 
int main()
{
    scanf("%s", str);
    int len = strlen(str);
    sma.init();
    for(int i = 0; i < len; i++) sma.SMA_Extend(str[i] - 'a');
    sma.last = 1;
    for(int i = len-1; i >= 0; i--) sma.SMA_Extend(str[i] - 'a');
    LL ans1 = sma.getSubNum();
 
    pam.init();
    for(int i = 0; i < len; i++){
        //printf("fdsjfkds\n");
        pam.Insert(str[i] - 'a');
    }
    //printf("   %d %d\n", pam.p-2, ans1);
    printf("%lld\n", (ans1 + pam.p - 2) / 2);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值