1. 程式人生 > >P3966 [TJOI2013]單詞

P3966 [TJOI2013]單詞

build fail using lin trie .org 優化 ref data

P3966 [TJOI2013]單詞

AC自動機

題意:輸入若幹個單詞,對於每個單詞,計算它在所有單詞中出現的次數(原題講的什麽鬼)。

attention:有重復單詞,還很多

顯然,我們可以用AC自動機來搞

但是對每個單詞都需要跑一遍過於費時。

於是我們想到用一個主串把這些單詞拼在一起,中間用特殊符號隔開。這樣我們就只要在主串上跑一遍AC自動機就可以了

但是這樣還不夠(90pts)

於是我們就要對AC自動機進行優化(以後都這樣寫吧qwq)。

引入一個last指針,保存某節點沿fail指針向前跳最近一個有結尾標記的節點,詢問時直接按last指針代替fail指針跳轉。搞定

對於重復單詞:用一個pre數組存儲某編號對應單詞的結尾節點

end.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct data{
    int nxt[26],end,fail,last;
}a[1000002];
int n,cnt,pre[202];
long long f[202];
string q,g;
inline void Trie_build(int id){
    cin>>q; g=g+"#"+q; //用string方便地加上各個單詞
    
int u=0,len=q.size(); for(int i=0;i<len;++i){ int p=q[i]-a; if(!a[u].nxt[p]) a[u].nxt[p]=++cnt; u=a[u].nxt[p]; } if(!a[u].end) a[u].end=id; pre[id]=a[u].end; //重復單詞處理 } inline void AC_build(){ queue <int> h; for(int i=0;i<26;++i) if(a[0].nxt[i]) h.push(a[0
].nxt[i]); while(!h.empty()){ int x=h.front(); h.pop(); for(int i=0;i<26;++i){ int &to=a[x].nxt[i]; if(to){ a[to].fail=a[a[x].fail].nxt[i]; a[to].last= a[a[to].fail].end ? a[to].fail:a[a[to].fail].last; //last指針優化 h.push(to); }else to=a[a[x].fail].nxt[i]; } } } inline void AC_query(){ int u=0,len=g.size(); for(int i=0;i<len;++i){ if(g[i]==#) {u=0; continue;} u=a[u].nxt[g[i]-a]; for(int j=u;j;j=a[j].last) ++f[a[j].end]; //按last指針跳轉 } for(int i=1;i<=n;++i) printf("%lld\n",f[pre[i]]); //輸出的是 指向的節點 的編號 的值 } int main(){ //freopen("P3966.in","r",stdin); scanf("%d",&n); for(int i=1;i<=n;++i) Trie_build(i); AC_build(); AC_query(); return 0; }

P3966 [TJOI2013]單詞