【BZOJ2754】【codevs2403】喵星球上的点名,AC自动机与STL的狂欢

Time:2016.05.17
Author:xiaoyimi
转载注明出处谢谢


传送门1
传送门2
思路:
(本来想在学习AC自动机的时候做这个题的,但发现要用vector,所以就弃了)
由于我们要求点名串是名字的子串,所以我们以点名串为模式串,建立AC自动机,并建立结尾节点与原串编号的映射,用名字进行匹配时沿fail往上寻找,如果之前没找到过就把这个节点所保存的结尾节点的大小siz都加上,也就是说当前名字可以在这个节点匹配上siz个点名串,同时这siz个点名串也可以对应这个名字,所以还要开一个数组记录自动机上每个结尾节点能匹配上的名字的数量
(可能说的有点拗口,读者老爷可以自己思考一下,或者看代码了解细节等等)
注意:
感谢Yveh的提醒让我走出误区
一开始纠结是名字还是点名串建自动机,听Yveh一说猛然想到自己以名字建自动机的方法是错误的
还有就是忘记了fail指针的神奇性质,最初的想法是从自动机每个节点的子树上寻找答案,悲伤的故事:-(
代码:

#include"bits/stdc++.h"
#define M 1000006
#define LL long long
using namespace std;
int n,m,root=1,cnt=1;
int fail[M],len[2][M/5],vis[M],sum[M],ans[M],hash[M>>1];//vis表示自动机上的节点处理询问时的访问情况,sum存储自动机节点上名字经过该节点的总次数,hash[i]表示第i个点名串在AC自动机上对应的结尾节点
map<int,int>trie[M];
vector<int> name[2][M/5],son[M],ID[M];
int in()
{
    int t=0;char ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    while (isdigit(ch)) t=(t<<1)+(t<<3)+ch-48,ch=getchar();
    return t;
}
void insert(int len,int id)
{
    int x,now=root;
    for (int i=0;i<len;i++)
    {
        x=in();
        if (!trie[now][x])
            trie[now][x]=++cnt,
            son[now].push_back(x);
        now=trie[now][x];
    }
    ID[now].push_back(id);
    hash[id]=now;
}
void build()
{
    queue<int>q;
    q.push(root);
    while(!q.empty())
    {
        int k=q.front();q.pop();
        for (int i=0;i<son[k].size();i++)
        {
            int tmp=fail[k],x=trie[k][son[k][i]];
            while (tmp&&!trie[tmp][son[k][i]]) tmp=fail[tmp];
            if (k!=root&&tmp) fail[x]=trie[tmp][son[k][i]];
            else fail[x]=root;
            q.push(x);
        }
    }
}
main()
{
    n=in();m=in();
    for (int i=1;i<=n;i++)
        for (int j=0;j<2;j++)
        {
            len[j][i]=in();
            for (int k=1;k<=len[j][i];k++)
                name[j][i].push_back(in());
        }
    for (int i=1;i<=m;i++)
        insert(in(),i);
    build();
    for (int i=1;i<=n;i++)
        for (int j=0;j<2;j++)
        {
            int now=root,tmp;
            for (int k=0;k<len[j][i];k++)
            {
                while (now&&!trie[now][name[j][i][k]]) now=fail[now];
                if (!now) now=root;
                else now=trie[now][name[j][i][k]];
                tmp=now;
                for (;tmp!=root;tmp=fail[tmp])
                    if (ID[tmp].size()&&vis[tmp]!=i)
                        vis[tmp]=i,
                        ans[i]+=ID[tmp].size(),
                        sum[tmp]++;
            }
        }
    for (int i=1;i<=m;i++) printf("%d\n",sum[hash[i]]);
    for (int i=1;i<n;i++) printf("%d ",ans[i]);
    printf("%d",ans[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值