题解 lg2336 [SCOI2012]喵星球上的点名 --ac自动机
由于我是看的题解写的,故一定要写这篇题解
题意
每只小猫有姓和名两个字符串.每次点名给一个字符串,如果点名的字符串中包含一只小猫的姓或名的子串,其必须答"到"
问
-
每次点名会有几只小猫答"到"
-
每只小猫会答几次"到"
思路
看到就有写AC自动机的冲动
这道题的思路有很多,什么 SA+莫队 , SA+树状数组,SAM,ACM乱搞等,不过我只会这篇题解讲的那种太菜了,就做一个进一步诠释吧
首先,姓和名根据题意是应该分开的,我们可以加一个不存在的字符作为分界线
第一次看题目我是想把名字串建AC自动机,点名串跑的,但这显然不行.(子串这个太毒瘤了)
正解是这样,把名字串和点名串都插进AC自动机中,然后构建trie树.trie树的性质是字符串\(A\)若能在\(fail\)树上跳到字符串\(B\),则\(B\)一定是\(A\)的后缀.当求出一个串所有前缀的所有后缀,就求出了这个串的所有子串.
然后跑树链剖分,求出trie树dfs序以及其它用以求lca的东东
接下来就是如何解决两个问题的作诠释环节
容易理解, 点名串 所能点到 名字串子串 都在trie树上其终止节点的子树中
第一问
对于一个名字串的每一个前缀(总前缀个数不超过字符串总长),覆盖它到根的路径(覆盖表示加多次算一次)。
对每一个名字串都这么做,看点名串总共被多少个名字串给覆盖。
树上链修改,单点查询的问题先转化成树上单点修改,子树查询的问题。
由于覆盖多次算只算一次,就要把覆盖多的部分减掉。
这里有一个小 trick。
对名字串的前缀按 dfs 序排序,减掉的部分就是每相邻节点的 lca。
这样就可以做覆盖多次算一次了。
将每个名字串的前缀在树状数组上加1,然后统计点名串终止节点在子树的和
不过每个点名串可能会有重复,就需要进行容斥.通过dfs序排序后在一个子树内的点会相邻,那么lca就可以消去影响了
第二问
对于一个名字串的所有一个前缀,看它们总共覆盖了多少点名串。
树上单点修改,链查询的问题先转化trie树上子树修改, 单点查询的问题。
同样利用上面的 trick,减掉 dfs 序相邻节点 lca 的贡献即可。
点名串所能点到名字串子串都在trie树上其的子树中,所以在树状数组上差分,进行区间修改,接着对于每一个名字串的前缀求前缀和,即可求出这一个名字串被几个点名串点到
重复的话同理
细节
这道题我还是收获挺多的
- map建AC自动机的技巧\(getfail()\),(其实感觉和普通的也没有太大区别)
- fail树上的操作
- 可以不重复代码的丫(指我的建AC自动机
又臭又长)
代码
我一定会再打一遍的
/*
* @Author: fpjo
* @Date: 2020-10-29 08:14:51
* @Last Modified by: fpjo
* @Last Modified time: 2020-10-29 11:06:43
*/
#include<bits/stdc++.h>
using namespace std;
int const N=5e4+10,M=1e5+10,MAXN=(N+M)<<1;
map<int,int>::iterator it;
int n,m,_,tott;
int h[MAXN],hson[MAXN],siz[MAXN],dep[MAXN],top[MAXN],dfn[MAXN],fa[MAXN];
struct edge{
int to,next;
}e[MAXN<<1];
void add(int u,int v){
e[++_].to=v,e[_].next=h[u],h[u]=_;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return f*x;
}
struct ACAutomaton{
int tot;
int nameed[MAXN],queryed[MAXN];
struct point{
map<int,int>Map;
int id,cntme,cntd,fail,fa;
}trie[MAXN];
queue<int>q;
void insertname(int id){
int len1=read();int now=0;
for(int i=1;i<=len1;i++){
int a=read();
if(!trie[now].Map.count(a)){
trie[now].Map[a]=++tot;
trie[tot].fa=now;
}
now=trie[now].Map[a];
}
if(!trie[now].Map.count(-1)){
trie[now].Map[-1]=++tot;
trie[tot].fa=now;
}
now=trie[now].Map[-1];
int len2=read();
for(int i=1;i<=len2;i++){
int a=read();
if(!trie[now].Map.count(a)){
trie[now].Map[a]=++tot;
trie[tot].fa=now;
}
now=trie[now].Map[a];
}
nameed[id]=now;
}
void insertquery(int id){
int len=read(),now=0;
for(int i=1;i<=len;i++){
int a=read();
if(!trie[now].Map.count(a)){
trie[now].Map[a]=++tot;
trie[tot].fa=now;
}
now=trie[now].Map[a];
}
queryed[id]=now;
}
int getfail(int f,int c){
if(trie[f].Map.count(c))return trie[f].Map[c];
else if(!f)return f;
return trie[f].Map[c]=getfail(trie[f].fail,c);
}
void buildfail(){
for(it = trie[0].Map.begin();it!=trie[0].Map.end();++it){
trie[it->second].fail=0;
q.push(it->second);
}
while(!q.empty()){
int x=q.front();q.pop();
for(it=trie[x].Map.begin();it!=trie[x].Map.end();++it){
trie[it->second].fail=getfail(trie[x].fail,it->first);
q.push(it->second);
}
}
for(int i=1;i<=tot;i++)add(trie[i].fail,i);
}
}ACA;
struct BIT{
int tree[MAXN];
#define lowbit(x) x&-x
void clear(){memset(tree,0,sizeof(tree));}
void insert(int x,int a){
for(;x<=MAXN;x+=lowbit(x))tree[x]+=a;
}
inline int query(int x){
int sum=0;
for(;x;x-=lowbit(x))sum+=tree[x];
return sum;
}
#undef lowbit
}Bit;
void dfs1(int x,int dad,int depth){
fa[x]=dad;siz[x]=1;dep[x]=depth;
int maxn=-1;
for(int i=h[x];i;i=e[i].next){
int to=e[i].to;
if(to==dad)continue;
dfs1(to,x,depth+1);
siz[x]+=siz[to];
if(maxn<siz[to])maxn=siz[to],hson[x]=to;
}
}
void dfs2(int x,int topf){
top[x]=topf;dfn[x]=++tott;
if(!hson[x])return;
dfs2(hson[x],topf);
for(int i=h[x];i;i=e[i].next){
int to=e[i].to;
if(to==fa[x] || to==hson[x])continue;
dfs2(to,to);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
if(dep[x]<dep[y])return x;
return y;
}
int arr[MAXN];
bool cmp(int a,int b){
return dfn[a]<dfn[b];
}
void solve1(){
Bit.clear();
for(int i=1;i<=n;i++){
int u=ACA.nameed[i],cnt=0;
while(u){
arr[++cnt]=u;
Bit.insert(dfn[u],1);
u=ACA.trie[u].fa;
}
sort(arr+1,arr+cnt+1,cmp);
for(int j=1;j<cnt;j++){
Bit.insert(dfn[lca(arr[j],arr[j+1])],-1);
//printf("test: %d\n",j);
}
//printf("hello\n");
}
//printf("what!\n");
for(int i=1;i<=m;i++){
int qid=ACA.queryed[i];
printf("%d\n",Bit.query(dfn[qid]+siz[qid]-1)-Bit.query(dfn[qid]-1));
}
}
void solve2(){
Bit.clear();
for(int i=1;i<=m;i++){
int u=ACA.queryed[i],cnt=0;
Bit.insert(dfn[u],1);
Bit.insert(dfn[u]+siz[u],-1);
}
for(int i=1;i<=n;i++){
int u=ACA.nameed[i];int cnt=0,ans=0;
while(u){
arr[++cnt]=u;
ans+=Bit.query(dfn[u]);
u=ACA.trie[u].fa;
}
sort(arr+1,arr+cnt+1,cmp);
for(int j=1;j<cnt;j++){
ans-=Bit.query(dfn[lca(arr[j],arr[j+1])]);
}
printf("%d ",ans);
}
printf("\n");
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)ACA.insertname(i);
for(int i=1;i<=m;i++)ACA.insertquery(i);
ACA.buildfail();
dfs1(0,0,0);
dfs2(0,0);
//printf("hello\n");
solve1();
solve2();
return 0;
}