【BZOJ2434】[NOI2011]阿狸的打字機
阿新 • • 發佈:2019-01-09
【BZOJ2434】[NOI2011]阿狸的打字機
題面
題解
我們先想一下最暴力是怎麼搞的
把$AC$自動機建好,每一個節點,從$y$串的結尾節點往上跳它的父親,
和普通的$AC$自動機一樣跳就好了
然而這個可以優化一下
我們將所有詢問離線
每個串統計一次其他串對它的貢獻
就可以有$70pts$了
$70pts$程式碼
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #include <queue> using namespace std; const int MAX_N = 2e5 + 5; char s[MAX_N]; int N, tot, nd[MAX_N], ans[MAX_N], sum[MAX_N]; struct Trie { int ch[26], fail, fa, End; } t[MAX_N]; void build() { static queue<int> que; for (int i = 0; i < 26; i++) if (t[0].ch[i]) que.push(t[0].ch[i]), t[t[0].ch[i]].fail = 0; while (!que.empty()) { int o = que.front(); que.pop(); for (int i = 0; i < 26; i++) { if (t[o].ch[i]) t[t[o].ch[i]].fail = t[t[o].fail].ch[i], que.push(t[o].ch[i]); else t[o].ch[i] = t[t[o].fail].ch[i]; } } } int query(int y) { int res = 0; for (int o = nd[y]; o; o = t[o].fa) for (int x = o; x; x = t[x].fail) if (t[x].End) ++sum[t[x].End]; return res; } struct Query { int x, y, id; } q[MAX_N]; bool operator < (const Query l, const Query r) { return l.y < r.y; } int main () { scanf("%s", s + 1); for (int o = 0, i = 1, l = strlen(s + 1); i <= l; i++) { if ('a' <= s[i] && s[i] <= 'z') { if (!t[o].ch[s[i] - 'a']) t[o].ch[s[i] - 'a'] = ++tot, t[tot].fa = o; o = t[o].ch[s[i] - 'a']; } if (s[i] == 'B') o = t[o].fa; if (s[i] == 'P') nd[++N] = o, t[o].End = N; } build(); int M; scanf("%d", &M); for (int i = 1; i <= M; i++) scanf("%d", &q[i].x), scanf("%d", &q[i].y), q[i].id = i; sort(&q[1], &q[M + 1]); for (int i = 1, j = 1; i <= M; i = j) { query(q[i].y); while (q[j].y == q[i].y) ans[q[j].id] = sum[q[j].x], ++j; fill(&sum[1], &sum[N + 1], 0); } for (int i = 1; i <= M; i++) printf("%d\n", ans[i]); return 0; }
然後我們想一下這個過程:
每一個點往上跳,如果可以跳到一個點,是其他字串的$End$節點就統計貢獻
所以對於一個點,它跳到它被匹配的模式串的$End$點的次數,就是答案
換句話說
對於每個模式串,就是要統計有多少個文字串能跳到它。
這個就好解決一些了,
發現對於每個點,它對應的$fail$只有一個
所以我們將點$i$與$fail_i$連邊,
在新的樹上統計:
每次將串$i$的鏈上加一,然後將詢問離線統計貢獻。
但是這樣做還是隻有$70pts$,
因為還是有很多狀態被重複統計,
那麼如何繼續優化呢?
接下來的做法就很巧妙了:
我們保留原來的$trie$和根據$fail$新建的樹
在新樹上預處理$dfs$序,
然後$dfs$遍歷一遍原$trie$,入棧時將這個點$+1$
出棧時$-1$
如果這個點有結尾的串,就對這個串統計貢獻,
因為我們$dfs$回答詢問時只會有那一個串被統計
所以我們的做法是對的。
如有什麼不理解的地方,可以參見程式碼
程式碼
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #include <queue> #include <vector> using namespace std; const int MAX_N = 2e5 + 5; char s[MAX_N]; int N, tot, nd[MAX_N], ans[MAX_N]; struct Trie { int ch[26], cpy[26], fail, fa, End; } t[MAX_N]; void build() { static queue<int> que; for (int i = 0; i < 26; i++) if (t[0].ch[i]) que.push(t[0].ch[i]), t[t[0].ch[i]].fail = 0; while (!que.empty()) { int o = que.front(); que.pop(); for (int i = 0; i < 26; i++) { if (t[o].ch[i]) t[t[o].ch[i]].fail = t[t[o].fail].ch[i], que.push(t[o].ch[i]); else t[o].ch[i] = t[t[o].fail].ch[i]; } } } struct Graph { int to, next; } e[MAX_N << 1]; int fir[MAX_N], e_cnt = 0; void clearGraph() { memset(fir, -1, sizeof(fir)); e_cnt = 0; } void Add_Edge(int u, int v) { e[e_cnt] = (Graph){v, fir[u]}, fir[u] = e_cnt++; } struct Query { int x, id; } ; vector<Query> vec[MAX_N]; int tim, L[MAX_N], R[MAX_N]; void dfs(int x) { L[x] = ++tim; for (int i = fir[x]; ~i; i = e[i].next) dfs(e[i].to); R[x] = tim; } int c[MAX_N]; inline int lb(int x) { return x & -x; } void add(int x, int v) { while (x <= tim) c[x] += v, x += lb(x); } int sum(int x) { int res = 0; while (x > 0) res += c[x], x -= lb(x); return res; } void DFS(int x) { add(L[x], 1); if (t[x].End) for (vector<Query> :: iterator ite = vec[t[x].End].begin(); ite != vec[t[x].End].end(); ++ite) ans[ite->id] = sum(R[nd[ite->x]]) - sum(L[nd[ite->x]] - 1); for (int i = 0; i < 26; i++) if (t[x].cpy[i]) DFS(t[x].cpy[i]); add(L[x], -1); } int main () { scanf("%s", s + 1); for (int o = 0, i = 1, l = strlen(s + 1); i <= l; i++) { if ('a' <= s[i] && s[i] <= 'z') { if (!t[o].ch[s[i] - 'a']) t[o].ch[s[i] - 'a'] = ++tot, t[tot].fa = o; o = t[o].ch[s[i] - 'a']; } if (s[i] == 'B') o = t[o].fa; if (s[i] == 'P') nd[++N] = o, t[o].End = N; } for (int i = 0; i <= tot; i++) for (int o = 0; o < 26; o++) t[i].cpy[o] = t[i].ch[o]; build(); clearGraph(); for (int i = 1; i <= tot; i++) Add_Edge(t[i].fail, i); dfs(0); int M; scanf("%d", &M); for (int i = 1; i <= M; i++) { int x, y; scanf("%d%d", &x, &y); vec[y].push_back((Query){x, i}); } DFS(0); for (int i = 1; i <= M; i++) printf("%d\n", ans[i]); return 0; }