[bzoj2780][Spoj]8093 Sevenk Love Oimaster

题目大意

给定n个字符串,q个询问,每个询问给定一个字符串,求它在n个字符串中多少个中以连续子串形式出现。

n<=10000, q<=60000
the total length of n strings<=100000,
the total length of q question strings<=360000

分析

首先很容易想到后缀自动机。
给n个字符串建广义后缀自动机,然后每个询问串就在自动机上跑到相应节点,然后在fail树子树上找有多少个不同的字符串。

那么可以离线读入询问,然后按照dfs序枚举,保留每个字符串最后出现的位置,查询操作就是一个数组内有多少个大于x的值。这个用树状数组维护即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>

using namespace std;

const int N=200005;

typedef long long LL;

int n,q,tot,last,ans[N],fail[N],step[N],st[N],m,en[N],D[N],dfn[N],h[N],E[N],nxt[N],fi[N],T[N],now[N];

map <int,int> e[N];

vector <int> Q[N],H[N];

char s[N<<1],t[N];

void add(char c)
{
    int p=last,np,q,nq;
    if (e[p][c]>0 && step[e[p][c]]==step[p]+1)
    {
        last=e[p][c];
        return;
    }
    for (step[last=np=++m]=step[p]+1;p>=0 && !e[p][c];p=fail[p]) e[p][c]=np;
    if (p<0) fail[np]=0;else
    {
        q=e[p][c];
        if (step[q]==step[p]+1) fail[np]=q;else
        {
            nq=++m;
            e[nq]=e[q];
            fail[nq]=fail[q];
            step[nq]=step[p]+1;
            fail[q]=fail[np]=nq;
            for (;p>=0 && e[p][c]==q;p=fail[p]) e[p][c]=nq;
        }
    }
}

void link(int x,int y)
{
    E[++tot]=y; nxt[tot]=h[x]; h[x]=tot;
}

void dfs(int x)
{
    dfn[x]=++tot;
    for (int i=h[x];i;i=nxt[i]) dfs(E[i]);
    en[x]=tot;
}

int lowbit(int x)
{
    return x&-x;
}

void change(int x,int y)
{
    for (;x<=tot;x+=lowbit(x)) T[x]+=y;
}

int sum(int x)
{
    int k=0;
    for (;x;x-=lowbit(x)) k+=T[x];
    return k;
}

int main()
{
    scanf("%d%d",&n,&q);
    fail[0]=-1;
    for (int i=0;i<n;i++)
    {
        scanf("%s",t);
        st[i+1]=st[i]+strlen(t);
        last=0;
        for (int j=0;j<st[i+1]-st[i];j++)
        {
            s[j+st[i]]=t[j];
            add(t[j]);
        }
    }
    for (int i=1;i<=m;i++) link(fail[i],i);
    tot=0;
    dfs(0);
    for (int i=0;i<n;i++)
    {
        for (int j=st[i],k=0;j<st[i+1];j++)
        {
            k=e[k][s[j]];
            H[dfn[k]].push_back(i);
        }
    }
    for (int i=0;i<q;i++)
    {
        scanf("%s",t);
        bool bz=1;
        int x=0;
        for (int j=0,k=strlen(t);j<k;j++)
        {
            if (!e[x][t[j]])
            {
                bz=0; break;
            }
            x=e[x][t[j]];
        }
        if (bz)
        {
            fi[i]=dfn[x]-1; Q[en[x]].push_back(i);
        }
    }
    for (int i=1;i<=tot;i++)
    {
        vector <int> ::iterator it;
        for (it=H[i].begin();it!=H[i].end();it++)
        {
            if (now[*it]>0) change(now[*it],-1);
            now[*it]=i;
            change(now[*it],1);
        }
        for (it=Q[i].begin();it!=Q[i].end();it++)
        {
            ans[*it]=sum(tot)-sum(fi[*it]);
        }
    }
    for (int i=0;i<q;i++) printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值