[題解] lg P2336 [SCOI2012]喵星球上的點名 --ac自動機
由於我是看的題解寫的,故一定要寫這篇題解
題意
每隻小貓有姓和名兩個字串.每次點名給一個字串,如果點名的字串中包含一隻小貓的姓或名的子串,其必須答"到"
問
-
每次點名會有幾隻小貓答"到"
-
每隻小貓會答幾次"到"
思路
看到就有寫AC自動機的衝動
這道題的思路有很多,什麼 SA+莫隊 , SA+樹狀陣列,SAM,ACM亂搞等,不過我只會這篇題解講的那種太菜了,就做一個進一步詮釋吧
首先,姓和名根據題意是應該分開的,我們可以加一個不存在的字元作為分界線
第一次看題目我是想把名字串建AC自動機,點名串跑的,但這顯然不行.(子串這個太毒瘤了)
正解是這樣,把名字串和點名串都插進AC自動機中,然後構建trie樹.trie樹的性質是字串\(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;
}