luogu P5357【模板】AC自動機(二次加強版)
阿新 • • 發佈:2021-01-01
傳送門
這主要想說一下AC自動機加上拓撲排序。
當我們建完AC自動機後,查詢的時候會因為跳好多次fail指標而超時,所以需要優化。
為了說話方便,假設我們已經建好了fail樹。
在匹配的時候,如果匹配到節點\(u\),那麼\(u\)的所有祖先代表的字首一定能匹配上(fail指標的性質),暴力的做法是把\(u\)到fail樹根的路徑的所有點都更新一遍。但想一想就知道,可以只更新點\(sum[u]\),最後再從樹葉往上更新一遍,即\(sum[v]= \sum sum[u]\)(\(u\)是\(v\)的兒子節點)。
優化的思路就是這樣,但其實我們不用把fail樹建出來,而利用一個性質:一個節點如果能被更新,那麼他的孩子節點肯定已經更新完了。所以記錄每一個點孩子個數(度數),然後利用拓撲排序就可以解決。
題目中可能有重複的模板串,所以每一個節點開了一個vector,用來存是哪一個模板串的結尾。
#include<cstdio> #include<iostream> #include<cmath> #include<algorithm> #include<cstring> #include<cctype> #include<vector> #include<queue> using namespace std; #define enter puts("") #define space putchar(' ') #define Mem(a, x) memset(a, x, sizeof(a)) #define In inline typedef long long ll; const int maxn = 2e6 + 5; const int maxN = 6e6 + 5; In ll read() { ll ans = 0; char ch = getchar(), las = ' '; while(!isdigit(ch)) las = ch, ch = getchar(); while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar(); if(las == '-') ans = -ans; return ans; } In void write(ll x) { if(x < 0) x = -x, putchar('-'); if(x >= 10) write(x / 10); putchar(x % 10 + '0'); } int n; char s[maxn]; int ch[maxN][26], f[maxN], num[maxN], cnt = 0; vector<int> pos[maxN]; In void Clear(int x) {Mem(ch[x], 0), f[x] = num[x] = 0;} In int C(char c) {return c - 'a';} In void insert(char* s, int id) { int m = strlen(s), now = 0; for(int i = 0; i < m; ++i) { int c = C(s[i]); if(!ch[now][c]) Clear(++cnt), ch[now][c] = cnt; now = ch[now][c]; } num[now] = 1; pos[now].push_back(id); } int du[maxN]; In void build() { queue<int> q; for(int i = 0; i < 26; ++i) if(ch[0][i]) q.push(ch[0][i]); while(!q.empty()) { int now = q.front(); q.pop(); for(int i = 0; i < 26; ++i) if(ch[now][i]) { f[ch[now][i]] = ch[f[now]][i], q.push(ch[now][i]); du[f[ch[now][i]]]++; //更新度數 } else ch[now][i] = ch[f[now]][i]; //這個else在拓撲中沒有用,但是匹配的時候如果不加這一句可能會超時。 } } int sum[maxn]; In void query(char* s) { int len = strlen(s), now = 0; for(int i = 0; i < len; ++i) { int c = C(s[i]); now = ch[now][c]; sum[now]++; } } int ans[maxn]; In void topo() { queue<int> q; for(int i = 0; i <= cnt; ++i) if(!du[i]) q.push(i); while(!q.empty()) { int now = q.front(), v = f[now]; q.pop(); for(int i = 0; i < (int)pos[now].size(); ++i) ans[pos[now][i]] = sum[now]; sum[v] += sum[now]; if(!--du[v]) q.push(v); } } int main() { n = read(); Clear(0); for(int i = 1; i <= n; ++i) { scanf("%s", s); insert(s, i); } build(); scanf("%s", s); query(s), topo(); for(int i = 1; i <= n; ++i) write(ans[i]), enter; return 0; }