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]);
}