[BZOJ3277]串(广义后缀自动机)

题目:

我是超链接

题解:

这题的建立后缀自动机的方法和上道题目一样

我们先简化一下问题,不考虑k,只考虑怎么找到子串个数。我们根据以前的经验,step[i]-step[fa[i]]的值就是以某个状态为后缀的不同子串的个数,这道题目虽然没有说明白,但子串相同的情况是允许的。

right集吗?好像也可以,但是如果加入【在所有字符串中出现k次】这个约束就不行了,因为出现k次我们是以儿子为【重点】来的,因为长的串都出现在k个串里了,那和ta后缀一样的父亲肯定也出现在这k个串里啊。但是right集的【重点】在父亲节点,我们似乎不好处理这个问题了

既然我们必须以后面为【重点】,那就在统计的时候把东西都累加到后面呗,因为儿子出现了,父亲也会出现。那我们把此状态所代表的所有不同子串(step[i]-step[fa[i]])都累加到自己的儿子身上,最后对于每个字符串,将每一位的贡献加起来就行了。

诶这样不会加重吗?儿子明明已经包括了父亲,但如果父亲也在这个字符串里不就加了两遍?(×)
要特别注意,他们加的时候位置是不相同的,每个位置累加的是每个以某个位置为结尾的后缀,所以需要累加进去
举个例子:aaa,c[1]=0、c[2]=1、c[3]=2、c[4]=2,这里的c[4]虽然没有初值,但以4状态结尾的有’a’和’aa’,所以并不会加重啊

那这个出现次数怎么办呢?我们直接将size[i] < k的点的初值直接赋值成0,其他的都赋值成step[i]-step[fa[i]]累加即可。

代码:

#include <cstdio>
#include <cstring>
#define LL long long
using namespace std;
const int N=200005;
char st[N];
int ch[N][30],fa[N],step[N],p,np,q,nq,last,cnt;
int tot,nxt[N],v[N],point[N],tot1,nxt1[N],v1[N],point1[N],size[N],a[N];
LL c[N];
void addline(int x,int y){++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;}
void addline1(int x,int y){++tot1; nxt1[tot1]=point1[x]; point1[x]=tot1; v1[tot1]=y;}
void insert(int c,int id)
{
    p=last; last=np=++cnt;
    step[np]=step[p]+1; addline1(id,np);
    while (p && !ch[p][c]) ch[p][c]=np,p=fa[p];
    if (!p) fa[np]=1;
    else
    {
    q=ch[p][c];
    if (step[q]==step[p]+1) fa[np]=q;
    else
    {
    nq=++cnt; step[nq]=step[p]+1;
    memcpy(ch[nq],ch[q],sizeof(ch[q]));
    nxt[nq]=nxt[q]; size[nq]=size[q];
    fa[nq]=fa[q]; fa[q]=fa[np]=nq;
    while (ch[p][c]==q) ch[p][c]=nq,p=fa[p];    
    }   
    }
    for (;np;np=fa[np]) 
      if (nxt[np]!=id) nxt[np]=id,size[np]++;
      else break;
}
void dfs(int x)
{
    c[x]+=c[fa[x]];
    for (int i=point[x];i;i=nxt[i]) 
      dfs(v[i]);
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    cnt=1;
    for (int i=1;i<=n;i++)
    {
        scanf("%s",st+1);
        last=1;int l=strlen(st+1);
        for (int j=1;j<=l;j++) insert(st[j]-'a',i);
    }
    for (int i=1;i<=cnt;i++) c[step[i]]++;
    for (int i=1;i<=cnt;i++) c[i]+=c[i-1];
    for (int i=1;i<=cnt;i++) a[c[step[i]]--]=i;

    for (int i=1;i<=cnt;i++)
    {
        int t=a[i];
        addline(fa[t],t);
        if (size[t]>=k) c[t]=step[t]-step[fa[t]];
        else c[t]=0;
    }
    dfs(1);
    for (int i=1;i<=n;i++)
    {
        LL ans=0;
        for (int j=point1[i];j;j=nxt1[j])
          ans+=c[v1[j]];
        printf("%lld ",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值