【BZOJ3473】字符串

Description

给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串?
Input

第一行两个整数n,k。
接下来n行每行一个字符串。
Output

一行n个整数,第i个整数表示第i个字符串的答案。
Sample Input

3 1

abc

a

ab

Sample Output

6 1 3
HINT

对于 100% 的数据,1<=n,k<=10^5,所有字符串总长不超过10^5,字符串只包含小写字母。

Source

Adera 1 杯冬令营模拟赛

对多串建一个SAM.我们在最后再讨论这个建法..(张天扬论文里的做法有误…

建好SAM,对每个节点放到SAM上运行,不断向Parent树的根节点上走,把路径上的节点都计个数.为了避免出现重复,记录每个节点最后一次被访问是在哪个串,然后串在运行的时候如果遇到了这个串已经访问过一遍的节点就停止.复杂度是 O(nn) …而且实际上比他还要小..其实我不知道这个复杂度怎么来的...
然后对每个节点看他到根的路径上有多少子串出现了k次以上,Parent树上DP一下得到答案,最后把每个串放在SAM上跑,过程中每个访问的节点的答案求和就是最终每个串答案

我们最后来说一下SAM的构建的问题..论文里说是在每个串的末尾加上一个不在原字符集中的字符来构建整个SAM,实际上好像是错的
这样构建会导致产生出很多奇怪的子串然后影响答案..
正确的姿势是每构建完一串把构建时候的初始节点变成SAM的root重新构建..

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAXN 200010
#define LL long long
using namespace std;
int n,k;
char ch[MAXN];
string s[MAXN>>1];
LL ans;
struct sam
{
    int a[MAXN][27],fa[MAXN],len[MAXN],vis[MAXN],c[MAXN];
    LL sum[MAXN];
    int p,q,np,nq,last,cnt;
    sam()   {last=++cnt;}
    void insert(int c)
    {
        p=last;np=last=++cnt;len[np]=len[p]+1;
        while (!a[p][c]&&p) a[p][c]=np,p=fa[p];
        if (!p) fa[np]=1;
        else
        {
            q=a[p][c];
            if (len[q]==len[p]+1)   fa[np]=q;
            else
            {
                nq=++cnt;len[nq]=len[p]+1;memcpy(a[nq],a[q],sizeof(a[q]));
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                while (a[p][c]==q)  a[p][c]=nq,p=fa[p];
            }
        }
    }
    void get_ans(int x)
    {
        if (x==1||vis[x])   return;
        vis[x]=1;   get_ans(fa[x]); sum[x]+=sum[fa[x]];
    }
}sam;
int main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",ch); int l=strlen(ch);   s[i]=(string)(ch);
        for (int j=0;j<l;j++)   sam.insert(ch[j]-'a');  sam.last=1;
    }
    for (int i=1;i<=n;i++)
    {
        int l=s[i].length();int x=1,now=i;
        for (int j=0;j<l;j++)
        {
            x=sam.a[x][s[i][j]-'a'];    int t=x;
            while (t&&sam.vis[t]!=now)  sam.vis[t]=now,sam.c[t]++,t=sam.fa[t];
        }
    }
    for (int i=1;i<=sam.cnt;i++)    sam.vis[i]=0;
    for (int i=1;i<=sam.cnt;i++)    sam.sum[i]=(sam.c[i]>=k)*(sam.len[i]-sam.len[sam.fa[i]]);
    for (int i=1;i<=sam.cnt;i++)    sam.get_ans(i);
    for (int i=1;i<=n;i++)
    {
        int x=1,l=s[i].length();ans=0;
        for (int j=0;j<l;j++)   x=sam.a[x][s[i][j]-'a'],ans+=sam.sum[x];
        printf("%lld ",ans);
    }
}

你可能感兴趣的:(后缀自动机,多串SAM,Parent树DP)