字符串专题 1

你说的对,但是只要不考试干啥都行。

听说这字符串专题后边还有打击复读,恐怖!后边多项式和生成函数还有百鸽笼,这算多项式吗!而且机房好冷!

省选难度标了,Day1 T2 和 Day2 T2 都是紫挺震撼。

CF547E Mike and Friends

犹记当年 tlecoders 写了个 SA + 主席树怒 T 90pts。然而 CF 上跑的飞快。

这题有不少做法,可以建 AC 自动机离线询问,变成 Trie 上一条链加(暴力)和 fail 树子树查询(树状数组),也可以 SA 判断能匹配的左右端点然后主席树维护,也可以 SAM 线段树合并。

保留 tlecoders 的代码缩进吧。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int n, num, m;
char t[400010];
int s[400010], sa[400010], rk[400010], rk2[400010], key[400010], cnt[400010], bel[400010];
int len[400010], ht[400010], pos[400010], id[400010];
void getsa() {
    int m = 127;
    for (int i = 1; i <= n; i++) {
        rk[i] = s[i];
        cnt[rk[i]]++;
    }
    for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; i--) sa[cnt[rk[i]]--] = i;
    for (int w = 1;; w <<= 1) {
        int p = 0;
        for (int i = n; i > n - w; i--) id[++p] = i;
        for (int i = 1; i <= n; i++) {
            if (sa[i] > w)
                id[++p] = sa[i] - w;
        }
        memset(cnt + 1, 0, m * 4);
        for (int i = 1; i <= n; i++) {
            key[i] = rk[id[i]];
            cnt[key[i]]++;
        }
        for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
        for (int i = n; i >= 1; i--) sa[cnt[key[i]]--] = id[i];
        memcpy(rk2 + 1, rk + 1, n * 4);
        p = 0;
        for (int i = 1; i <= n; i++) {
            rk[sa[i]] = (rk2[sa[i]] == rk2[sa[i - 1]] && rk2[sa[i] + w] == rk2[sa[i - 1] + w]) ? p : ++p;
        }
        if (p == n) {
            for (int i = 1; i <= n; i++) sa[rk[i]] = i;
            break;
        }
        m = p;
    }
}
void getht() {
    for (int i = 1, k = 0; i <= n; i++) {
        if (rk[i] == 0)
            continue;
        if (k)
            k--;
        while (s[i + k] == s[sa[rk[i] - 1] + k]) k++;
        ht[rk[i]] = k;
    }
}
struct ST {
    int st[400010][21];
    void build() {
        for (int i = 1; i <= n; i++) st[i][0] = ht[i];
        for (int j = 1; j <= __lg(n); j++) {
            for (int i = 1; i + (1 << j) - 1 <= n; i++) {
                st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
            }
        }
    }
    int lcp(int l, int r) {
        if (l == r)
            return n - l + 1;
        l++;
        int k = __lg(r - l + 1);
        return min(st[l][k], st[r - (1 << k) + 1][k]);
    }
} st;
int getl(int id) {
    int l = 1, r = pos[id];
    while (l < r) {
        int mid = (l + r) >> 1;
        if (st.lcp(mid, pos[id]) < len[id])
            l = mid + 1;
        else
            r = mid;
    }
    return l;
}
int getr(int id) {
    int l = pos[id], r = n;
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (st.lcp(pos[id], mid) < len[id])
            r = mid - 1;
        else
            l = mid;
    }
    return l;
}
namespace Seg {
#define lson tree[rt].ls
#define rson tree[rt].rs
int t, rt[400010];
struct node {
    int ls, rs, sum;
} tree[400010 << 5];
void pushup(int rt) { tree[rt].sum = tree[lson].sum + tree[rson].sum; }
void build(int &rt, int l, int r) {
    rt = ++t;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    build(lson, l, mid);
    build(rson, mid + 1, r);
}
void update(int &rt, int pre, int L, int R, int pos) {
    tree[rt = ++t] = tree[pre];
    if (L == R) {
        tree[rt].sum++;
        return;
    }
    int mid = (L + R) >> 1;
    if (pos <= mid)
        update(lson, tree[pre].ls, L, mid, pos);
    else
        update(rson, tree[pre].rs, mid + 1, R, pos);
    pushup(rt);
}
int query(int x, int y, int L, int R, int l, int r) {
    if (l <= L && R <= r)
        return tree[y].sum - tree[x].sum;
    int mid = (L + R) >> 1, val = 0;
    if (l <= mid)
        val += query(tree[x].ls, tree[y].ls, L, mid, l, r);
    if (mid < r)
        val += query(tree[x].rs, tree[y].rs, mid + 1, R, l, r);
    return val;
}
}
int main() {
    scanf("%d%d", &num, &m);
    for (int i = 1; i <= num; i++) {
        scanf("%s", t + 1);
        len[i] = strlen(t + 1);
        pos[i] = n + 1;
        for (int j = 1; j <= len[i]; j++) {
            s[++n] = t[j] - 'a' + 1;
            bel[n] = i;
        }
        n++;
    }
    getsa();
    getht();
    st.build();
    Seg::build(Seg::rt[0], 0, num);
    for (int i = 1; i <= n; i++) Seg::update(Seg::rt[i], Seg::rt[i - 1], 0, num, bel[sa[i]]);
    for (int i = 1; i <= num; i++) pos[i] = rk[pos[i]];
    while (m--) {
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        int L = getl(k), R = getr(k);
        printf("%d\n", Seg::query(Seg::rt[L - 1], Seg::rt[R], 0, num, l, r));
    }
    return 0;
}

CF504E Misha and LCP on Tree

前边几个都挺板板的,这题 *3000 大概率因为卡常。

直接二分哈希,单次要求找 \(k\) 级祖先,可以写长剖 \(O(1)\),我学了个 \(O(\log\log n)\) 的重剖写法,就是预处理跳 \(2^k\) 条重链的结果。然后总复杂度就是 \(O(n\log n\log\log n)\) 了。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int mod=1000000007,prm=131,invprm=190839696;
int n,m,p[300010],invp[300010];
char s[300010];
struct node{
    int v,next;
}edge[600010];
int t,head[300010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int num,dfn[300010],dep[300010],fa[300010],rk[300010],top[300010][5],sz[300010],son[300010];
int up[300010],down[300010];
void dfs1(int x,int f){
    sz[x]=1;dep[x]=dep[f]+1;fa[x]=f;
    up[x]=(1ll*s[x]*p[dep[f]]+up[f])%mod;
    down[x]=(1ll*down[f]*prm+s[x])%mod;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs1(edge[i].v,x);
            sz[x]+=sz[edge[i].v];
            if(sz[son[x]]<sz[edge[i].v])son[x]=edge[i].v;
        }
    }
}
void dfs2(int x,int f,int tp){
    dfn[x]=++num;rk[num]=x;top[x][0]=tp;
    for(int i=1;i<=4;i++)top[x][i]=top[fa[top[x][i-1]]][i-1];
    if(son[x])dfs2(son[x],x,tp);
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f&&edge[i].v!=son[x])dfs2(edge[i].v,x,edge[i].v);
    }
}
int query(int x,int k){
    for(int i=4;i>=0;i--)if(top[x][i]&&k>dep[x]-dep[top[x][i]])k-=dep[x]-dep[top[x][i]]+1,x=fa[top[x][i]];
    return !x?0:rk[dfn[x]-k];
}
int lca(int x,int y){
    while(top[x][0]!=top[y][0]){
        if(dep[top[x][0]]<dep[top[y][0]])swap(x,y);
        x=fa[top[x][0]];
    }
    if(dep[x]>dep[y])swap(x,y);
    return x;
}
int getup(int x,int y){
    y=fa[y];
    return 1ll*(up[x]-up[y]+mod)%mod*invp[dep[y]]%mod;
}
int getdown(int x,int y){
    return (down[y]-1ll*down[x]*p[dep[y]-dep[x]]%mod+mod)%mod;
}
int get(int u,int v,int lc,int len){
    int d=dep[u]-dep[lc]+1;
    if(len<=d){
        v=query(u,len-1);
        return getup(u,v);
    }
    int hs1=getup(u,lc);len-=d;
    d=dep[v]-dep[lc]-len;
    v=query(v,d);
    int hs2=getdown(lc,v);
    return (1ll*hs1*p[len]+hs2)%mod;
}
bool check(int mid,int a,int b,int c,int d,int lcab,int lccd){
    return get(a,b,lcab,mid)==get(c,d,lccd,mid);
}
int main(){
	scanf("%d%s",&n,s+1);p[0]=invp[0]=1;
    for(int i=1;i<=n;i++)p[i]=1ll*p[i-1]*prm%mod,invp[i]=1ll*invp[i-1]*invprm%mod;
	for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
	dfs1(1,0);dfs2(1,0,1);
    scanf("%d",&m);
    while(m--){
        int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d);
        int lcab=lca(a,b),lccd=lca(c,d);
        int l=0,r=min(dep[a]+dep[b]-2*dep[lcab]+1,dep[c]+dep[d]-2*dep[lccd]+1);
        while(l<r){
            int mid=(l+r+1)>>1;
            if(check(mid,a,b,c,d,lcab,lccd))l=mid;
            else r=mid-1;
        }
        printf("%d\n",l);
    }
	return 0;
}

CF204E Little Elephant and Strings

广义 SAM,然后对于每个字符在 SAM 上的位置类似虚树一样按 dfs 序排序,每个点权值加,相邻 lca 权值减,最后在每个位置倍增跳一下。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int n,k;
string s[100010];
long long ans;
int pos[200010];
struct SAM{
    int cnt,fa[200010],len[200010],trie[200010][26];
    SAM(){cnt=1;}
    int ins(int ch,int last){
        if(trie[last][ch]){
            int p=last,q=trie[p][ch];
            if(len[p]+1==len[q])return q;
            else{
                len[++cnt]=len[p]+1;
                for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
                fa[cnt]=fa[q];fa[q]=cnt;
                while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
                return cnt;
            }
        }
        int p=last;last=++cnt;
        len[last]=len[p]+1;
        while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
        if(!p){
            fa[last]=1;return last;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;return last;
        }
        len[++cnt]=len[p]+1;
        for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
        fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
        return last;
    }
    void calc(string s){
        int p=1;
        for(int i=0;i<s.length();i++){
            p=trie[p][s[i]-'a'];
            pos[i]=p;
        }
    }
}sam;
struct node{
    int v,next;
}edge[200010];
int t,head[200010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int num,sz[200010],fa[200010][20],dep[200010],dfn[200010];
void dfs1(int x,int f){
    fa[x][0]=f;dep[x]=dep[f]+1;dfn[x]=++num;
    for(int i=1;i<=__lg(sam.cnt);i++)fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f)dfs1(edge[i].v,x);
    }
}
bool cmp(int a,int b){
    return dfn[a]<dfn[b];
}
int lca(int x,int y){
    if(dep[x]>dep[y])swap(x,y);
    for(int i=__lg(sam.cnt);i>=0;i--)if(dep[fa[y][i]]>=dep[x])y=fa[y][i];
    if(x==y)return x;
    for(int i=__lg(sam.cnt);i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
void dfs2(int x,int f){
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs2(edge[i].v,x);
            sz[x]+=sz[edge[i].v];
        }
    }
}
int getans(int x){
    if(sz[x]>=k)return sam.len[x];
    for(int i=__lg(sam.cnt);i>=0;i--)if(fa[x][i]&&sz[fa[x][i]]<k)x=fa[x][i];
    return sam.len[fa[x][0]];
}
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        int last=1;
        for(int j=0;j<s[i].length();j++)last=sam.ins(s[i][j]-'a',last);
    }
    for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
    dfs1(1,0);
    for(int i=1;i<=n;i++){
        sam.calc(s[i]);
        sort(pos,pos+s[i].length(),cmp);
        for(int j=0;j<s[i].length();j++)sz[pos[j]]++;
        for(int j=1;j<s[i].length();j++)sz[lca(pos[j],pos[j-1])]--;
    }
    dfs2(1,0);
    for(int i=1;i<=n;i++){
        sam.calc(s[i]);ans=0;
        for(int j=0;j<s[i].length();j++)ans+=getans(pos[j]);
        cout<<ans<<' ';
    }
    cout<<'\n';
    return 0;
}

CF1012D AB-Strings

首先连续若干个可以缩一个,然后睡大觉。胡了一下差不多是每次两边一起消掉一段相同字符的样子,然后看题解发现还要巨大分讨避免长度不平均。tourist 题解也挺语焉不详的。

写 nm,专题 2 都开了。

gym103427M String Problem

这一场怎么不是原神就是 A-SOUL。

首先显然答案是后缀。发现这是个类 Lyndon 分解状物,于是考虑什么样的后缀可以作为答案。答案一定是上一次答案的后缀,证明见 Lyndon 分解。假设一个后缀添上一个字符可能成为答案,那么它一定是上一次答案的一个 border,证明考虑非 border 字典序一定比开头小。那么暴力维护复杂度就是线性的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
char s[1000010],a[1000010];
int nxt[1000010];
int main(){
    scanf("%s",s+1);n=strlen(s+1);
    puts("1 1");
    a[1]=s[1];
    for(int i=2,j,len=1;i<=n;i++){
        while(len&&a[nxt[len]+1]<s[i])len=nxt[len];
        a[++len]=s[i];
        printf("%d %d\n",i-len+1,i);
        j=nxt[len-1];
        while(j&&a[len]!=a[j+1])j=nxt[j];
        if(len!=1&&a[len]==a[j+1])j++;
        nxt[len]=j;
    }
}

gym103409J

看不懂题面,感恩!原来我没有大脑,那没事了!

首先二分出这个 \(k\) 在哪个长度范围内。然后看字典序限制怎么做。

如果没有长度限制怎么做?我不会。我菜!建出后缀树,按 dfs 序扫就是子串的字典序。处理字典序可以记录每个节点最大 endpos 然后减长度得到首字母。

然后加上限制,发现每个节点能贡献的是一段长度区间,那线段树扫描线一下就没了。

然而卡空间,挺没木琴的。SAM 要用 map 存。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
int n,m;
char s[1000010];
struct SAM{
    int cnt,last,fa[2000010],len[2000010],pos[2000010];
    map<int,int>trie[2000010];
    SAM(){cnt=last=1;}
    void ins(int ch,int id){
        int p=last;last=++cnt;pos[last]=id;
        len[last]=len[p]+1;
        while(p&&trie[p].find(ch)==trie[p].end())trie[p][ch]=cnt,p=fa[p];
        if(!p){
            fa[last]=1;return;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;return;
        }
        len[++cnt]=len[p]+1;
        trie[cnt]=trie[q];
        fa[cnt]=fa[q];pos[cnt]=pos[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
    }
}sam;
struct gra{
    int v,next;
}edge[2000010];
int tot,head[2000010];
void add(int u,int v){
    edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
void dfs1(int x){
    for(int i=head[x];i;i=edge[i].next){
        dfs1(edge[i].v);
        sam.pos[x]=max(sam.pos[x],sam.pos[edge[i].v]);
    }
}
int num,dfn[2000010],rk[2000010];
vector<int>g[1000010];
void dfs2(int x){
    dfn[x]=++num;rk[num]=x;
    vector<pair<char,int> >tmp;
    for(int i=head[x];i;i=edge[i].next){
        tmp.push_back(make_pair(s[sam.pos[edge[i].v]-sam.len[x]],edge[i].v));
    }
    sort(tmp.begin(),tmp.end());
    for(pair<char,int> p:tmp){
        int v=p.second;
        g[sam.len[x]+1].push_back(v);
        g[sam.len[v]+1].push_back(-v);
        dfs2(v);
    }
}
struct Ques{
    long long k;
    int id;
    bool operator<(const Ques&s)const{
        return k<s.k;
    }
}q[1000010];
pair<int,int>ans[1000010];
#define lson rt<<1
#define rson rt<<1|1
int tree[8000010];
void pushup(int rt){
    tree[rt]=tree[lson]+tree[rson];
}
void update(int rt,int L,int R,int pos,int val){
    if(L==R){
        tree[rt]+=val;return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid)update(lson,L,mid,pos,val);
    else update(rson,mid+1,R,pos,val);
    pushup(rt);
}
int query(int rt,int L,int R,int k){
    if(L==R)return rk[L];
    int mid=(L+R)>>1;
    if(k<=tree[lson])return query(lson,L,mid,k);
    else return query(rson,mid+1,R,k-tree[lson]);
}
int main(){
    scanf("%s",s+1);n=strlen(s+1);
    reverse(s+1,s+n+1);
    for(int i=1;i<=n;i++)sam.ins(s[i]-'a',i);
    for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
    dfs1(1);dfs2(1);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)scanf("%lld",&q[i].k),q[i].id=i,ans[i]=make_pair(-1,-1);
    sort(q+1,q+m+1);
    long long sum=0;
    int p=1;
    for(int i=1;i<=n;i++){
        for(int x:g[i]){
            if(x>0)update(1,1,sam.cnt,dfn[x],1);
            else update(1,1,sam.cnt,dfn[-x],-1);
        }
        long long ret=sum;sum+=tree[1];
        while(p<=m&&q[p].k>ret&&q[p].k<=sum){
            int pos=query(1,1,sam.cnt,q[p].k-ret);
            ans[q[p].id]=make_pair(n-sam.pos[pos]+1,n-sam.pos[pos]+i);
            p++;
        }
    }
    for(int i=1;i<=m;i++)printf("%d %d\n",ans[i].first,ans[i].second);
    return 0;
}

CF914F Substrings in a String

典中典之 bitset 字符串匹配。详见 https://www.cnblogs.com/gtm1514/p/16909525.html。

CF700E Cool Slogans

看见串串没思路先建个 SAM 出来。然后考虑这个序列一定是 parent 树上从上往下一个序列(可以间断),发现“出现两次”这个限制可以在 parent 树上比较轻松地维护。线段树合并维护 endpos,然后设当前节点 \(x\) 从节点 \(pre\) 转移过来,那么在 \(pre\) 的线段树上查,对于 \(x\) 的一个位置 \(pos\) 若在 \([pos-len_x+len_{pre},pos-1]\) 里出现了 \(pre\) 则可以转移。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int n;
char s[200010];
struct SAM{
    int cnt,last,fa[400010],len[400010],trie[400010][26],pos[400010];
    SAM(){cnt=last=1;}
    void ins(int ch,int id){
        int p=last;last=++cnt;pos[last]=id;
        len[last]=len[p]+1;
        while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
        if(!p){
            fa[last]=1;return;
        }
        int q=trie[p][ch];
        if(len[p]+1==len[q]){
            fa[last]=q;return;
        }
        len[++cnt]=len[p]+1;
        for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
        fa[cnt]=fa[q];pos[cnt]=pos[q];fa[q]=cnt;fa[last]=cnt;
        while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
    }
}sam;
struct gra{
    int v,next;
}edge[400010];
int tot,head[400010];
void add(int u,int v){
    edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
struct node{
    #define lson tree[rt].ls
    #define rson tree[rt].rs
    int ls,rs,sum;
}tree[400010<<6];
int t,rt[400010];
void pushup(int rt){
    tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void update(int &rt,int L,int R,int pos){
    if(!rt)rt=++t;
    if(L==R){
        tree[rt].sum++;return;
    }
    int mid=(L+R)>>1;
    if(pos<=mid)update(lson,L,mid,pos);
    else update(rson,mid+1,R,pos);
    pushup(rt);
}
int query(int rt,int L,int R,int l,int r){
    if(!rt)return 0;
    if(l<=L&&R<=r)return tree[rt].sum;
    int mid=(L+R)>>1,val=0;
    if(l<=mid)val+=query(lson,L,mid,l,r);
    if(mid<r)val+=query(rson,mid+1,R,l,r);
    return val;
}
int merge(int x,int y,int l,int r){
    if(!x||!y)return x|y;
    int rt=++t;
    if(l==r){
        tree[rt].sum=tree[x].sum+tree[y].sum;
        return rt;
    }
    int mid=(l+r)>>1;
    tree[rt].ls=merge(tree[x].ls,tree[y].ls,l,mid);
    tree[rt].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
    pushup(rt);
    return rt;
}
void dfs1(int x){
    for(int i=head[x];i;i=edge[i].next){
        dfs1(edge[i].v);
        rt[x]=merge(rt[x],rt[edge[i].v],1,n);
    }
}
void getpre(){
    for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
    int p=1;
    for(int i=1;i<=n;i++){
        p=sam.trie[p][s[i]-'a'];
        update(rt[p],1,n,i);
    }
    dfs1(1);
}
int ans,dp[400010],pre[400010];
void dfs(int x,int f){
    if(!f);
    else if(f==1){
        dp[x]=1;pre[x]=x;
    }
    else{
        if(query(rt[pre[f]],1,n,sam.pos[x]-sam.len[x]+sam.len[pre[f]],sam.pos[x]-1))dp[x]=dp[f]+1,pre[x]=x;
        else dp[x]=dp[f],pre[x]=pre[f];
    }
    ans=max(ans,dp[x]);
    for(int i=head[x];i;i=edge[i].next)dfs(edge[i].v,x);
}
int main(){
    scanf("%d%s",&n,s+1);
    for(int i=1;i<=n;i++)sam.ins(s[i]-'a',i);
    getpre();
    dfs(1,0);
    printf("%d\n",ans);
    return 0;
}
posted @ 2023-05-18 10:32  gtm1514  阅读(27)  评论(0编辑  收藏  举报