Luogu2414 [NOI2011]阿貍的打字機
阿新 • • 發佈:2019-03-31
++ sta 深入理解 dfs bug 顯示 有一個 輸入 離開 (其中\(1 \leq x, y \leq n\)),打字機會顯示第\(x\)個打印的字符串在第\(y?\)個打印的字符串中出現了多少次。
,每到一個節點的時候在\(BIT\)裏對於這一個節點加\(1\),離開的時候再減掉。這樣我們在處理\(y\)串為當前串的詢問時,我們就直接對\(x\)串末尾節點所在的子樹求和就可以了
題目藍鏈
Description
打字機上只有28個按鍵,分別印有26個小寫英文字母和B
、P
兩個字母。經阿貍研究發現,這個打字機是這樣工作的:
- 輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最後)
- 按一下印有
B
的按鍵,打字機凹槽中最後一個字母會消失 - 按一下印有
P
的按鍵,打字機會在紙上打印出凹槽中現有的所有字母並換行,但凹槽中的字母不會消失
例如,阿貍輸入aPaPBbP
,紙上被打印的字符如下:
a aa ab
我們把紙上打印出來的字符串從\(1\)開始順序編號,一直到\(n\)。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數\((x,y)\)
阿貍發現了這個功能以後很興奮,他想寫個程序完成同樣的功能,你能幫助他麽?
Solution
我們首先對於所有的串構建出它們的AC自動機,我們發現問題轉換為了求\(y\)串到根節點的路徑中有多少個節點處於\(fail\)樹中\(x\)串結尾節點的子樹中(相當於枚舉\(y\)串的每一個前綴,判斷\(x\)是不是這個前綴的後綴)
我們對於\(fail\)樹與處理出每一個節點的\(dfn\)以及\(size\),用一個\(BIT\)去維護這個\(dfn\)序列。然後我們對字典樹進行一遍\(dfs\)
Code
#include <bits/stdc++.h> using namespace std; #define fst first #define snd second #define mp make_pair #define squ(x) ((LL)(x) * (x)) #define debug(...) fprintf(stderr, __VA_ARGS__) typedef long long LL; typedef pair<int, int> pii; template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; } template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; } inline int read() { int sum = 0, fg = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') fg = -1; for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30); return fg * sum; } const int maxn = 1e5 + 10; int n, m, p[maxn], ans[maxn]; char S[maxn]; vector<pii> Q[maxn]; namespace ACAM { int cnt, id, Index; int fa[maxn], son[maxn][26], fail[maxn], dfn[maxn], sz[maxn]; vector<int> B[maxn], g[maxn]; inline void init(char *s) { cnt = id = Index = 0; int now = 0; ++cnt; while (*s) { int c = *s - 'a'; if (*s == 'B') now = fa[now]; else if (*s == 'P') p[++id] = now, B[now].push_back(id); else { if (!son[now][c]) son[now][c] = ++cnt, fa[cnt] = now; now = son[now][c]; } ++s; } } inline void build() { static int tmp[maxn][26]; memcpy(tmp, son, sizeof tmp); queue<int> q; for (int i = 0; i < 26; i++) if (son[0][i]) q.push(son[0][i]), fail[son[0][i]] = 0, g[0].push_back(son[0][i]); while (!q.empty()) { int now = q.front(); q.pop(); for (int i = 0; i < 26; i++) if (son[now][i]) fail[son[now][i]] = son[fail[now]][i], g[son[fail[now]][i]].push_back(son[now][i]), q.push(son[now][i]); else son[now][i] = son[fail[now]][i]; } memcpy(son, tmp, sizeof son); } namespace BIT { #define lowbit(x) ((x) & (-(x))) int A[maxn]; inline void change(int x, int v) { for (int i = x; i <= cnt; i += lowbit(i)) A[i] += v; } inline int sum(int x) { int res = 0; for (int i = x; i >= 1; i -= lowbit(i)) res += A[i]; return res; } inline int query(int x, int y) { return sum(y) - sum(x - 1); } #undef lowbit } inline void dfs_fail(int x) { dfn[x] = ++Index, sz[x] = 1; for (int y : g[x]) dfs_fail(y), sz[x] += sz[y]; } inline void dfs(int x) { BIT::change(dfn[x], 1); for (pii y : Q[x]) ans[y.snd] = BIT::query(dfn[y.fst], dfn[y.fst] + sz[y.fst] - 1); for (int i = 0; i < 26; i++) if (son[x][i]) dfs(son[x][i]); BIT::change(dfn[x], -1); } } int main() { #ifdef xunzhen freopen("print.in", "r", stdin); freopen("print.out", "w", stdout); #endif static char S[maxn]; scanf("%s", S), n = strlen(S); ACAM::init(S), ACAM::build(), ACAM::dfs_fail(0); m = read(); for (int i = 1; i <= m; i++) { int x = p[read()], y = p[read()]; Q[y].push_back(mp(x, i)); } ACAM::dfs(0); for (int i = 1; i <= m; i++) printf("%d\n", ans[i]); return 0; }
Summary
最近思維僵化得有點嚴重,我一直在想怎麽用\(y\)串去查詢,沒想到其實可以反過來用\(x\)串去查詢
總的來說,這是一道深入理解AC自動機的好題(本人太菜求大佬輕噴)
Luogu2414 [NOI2011]阿貍的打字機