【SCOI2012】喵星球上的点名

Description

有n个串,代表n个人的姓氏和名字,都是用很多个数字表示的,比如我姓1,2,3,4,名4,5,6,7。然后有m个点名串,如果点到了某个人的姓或名里面的某一串,那个人就被点到,不过一个人在一个点名串中只能被点一次。比如点名串是2,3,4,我的姓中含有2,3,4,那么我就会被叫到。求每个学生分别被叫到多少次,和每个点名串分别叫到多少个学生。

Solution

这一题,很明显是用一个个的姓氏和名字在点名树中匹配,那么用到的算法明显是ac_automation(ac自动机)。那么为什么不是用一个个点名串在姓名树中匹配呢——>因为如果这样做,那么就有一颗姓树和一颗名树,明显比建造点名树要麻烦的多。然后套了ac自动机的模板,还要考虑一件事(这个坑了我好久,我还以为没有重复串),点名串可能有重复串(不用管姓名串了,因为我们是逐个匹配),如果有重复的姓名串,那么还要用一个数组存此节点含有那些点名串,然后统计答案。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
char s[10005];
bool bz[100005],cz[100005];
map<int,int> trie[100005];
int i,j,k,l,t,n,m,tot,x,num,da;
vector<int> a[1000005],b,c,d[100005],biao[1000005];
int data[1000005],ans[1000005],ans1[1000005],next[1000005],sum[1000005];
void build_ac_automation(int root){
int i,now,head=0,tail=1;
data[1]=root;
while(head<=tail){
now=data[++head];
for(map<int,int>:: iterator ii=trie[now].begin();ii!=trie[now].end();ii++){
i=ii->first;
if(trie[now][i]==0)continue;
int x=trie[now][i];
if(now>1){
j=next[now];
while(j>=1&&trie[j][i]==0) j=next[j];
if(j<1) next[x]=1;
else next[x]=trie[j][i];
}
if(now==1) next[x]=1;
data[++tail]=x;
}
}
}
int main(){
scanf("%d%d",&n,&m);
fo(i,1,n){
scanf("%d",&l);
fo(j,1,l){
scanf("%d",&t);
a[i].push_back(t);
}
a[i].push_back(-1);
scanf("%d",&l);
fo(j,1,l){
scanf("%d",&t);
a[i].push_back(t);
}
}
num=1;
fo(i,1,m){
scanf("%d",&l);
x=1;
fo(j,1,l){
scanf("%d",&t);
if(trie[x][t]==0){
trie[x][t]=++num;
x=trie[x][t];
}
else x=trie[x][t];
}
biao[x].push_back(i);
}
build_ac_automation(1);
fo(l,1,n){
x=1;
for(i=0;i<a[l].size();i++){
int u=a[l][i];
while(x&&!trie[x][u])x=next[x];
x=trie[x][u];
if(x==0) x=1;
for(j=x;j;j=next[j]){
for(k=0;k<biao[j].size();k++){
if(!cz[biao[j][k]]){
cz[biao[j][k]]=1;
c.push_back(biao[j][k]);
ans[biao[j][k]]++;
ans1[l]++;
}
}
}
}
for(i=0;i<c.size();i++) cz[c[i]]=0;
c.clear();b.clear();
}
fo(i,1,m) printf("%d\n",ans[i]);
fo(i,1,n) printf("%d ",ans1[i]);
}
}