【NOI 2011】阿狸的打字机
Problem
Description
阿貍喜歡收藏各種稀奇古怪的東西,最近他淘到一臺老式的打字機。打字機上只有 \(28\) 個按鍵,分別印有 \(26\) 個小寫英文字母和 B 、 P 兩個字母。 經阿貍研究發現,這個打字機是這樣工作的:
- 輸入小寫字母,打字機的一個凹槽中會加入這個字母(按 P 前凹槽中至少有一個字母)。
- 按一下印有 B 的按鍵,打字機凹槽中最后一個字母會消失。
- 按一下印有 P 的按鍵,打字機會在紙上打印出凹槽中現有的所有字母并換行,但凹槽中的字母不會消失(保證凹槽中至少有一個字母)。
例如,阿貍輸入 aPaPBbP ,紙上被打印的字符如下:
a aa ab我們把紙上打印出來的字符串從 \(1\) 開始順序編號,一直到 \(n\) 。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數 \((x,y)\) (其中 \(1 \le x,y \le n\) ),打字機會顯示第 \(x\) 個打印的字符串在第 \(y\) 個打印的字符串中出現了多少次。
阿貍發現了這個功能以后很興奮,他想寫個程序完成同樣的功能,你能幫助他么?
Input Format
輸入的第一行包含一個字符串,按阿貍的輸入順序給出所有阿貍輸入的字符。
第二行包含一個整數 \(m\) ,表示詢問個數。 接下來 \(m\) 行描述所有由小鍵盤輸入的詢問。其中第i行包含兩個整數 \(x, y\) ,表示第i個詢問為 \((x, y)\) 。
Output Format
輸出 \(m\) 行,其中第 \(i\) 行包含一個整數,表示第 \(i\) 個詢問的答案。
Sample
Input
aPaPBbP 3 1 2 1 3 2 3Output
2 1 0Range
所有測試數據的范圍和特點如下表所示:
| 1 | \(1\le n \le 100\) | \(1\le m \le 1000\) | - | \(\le 100\) |
| 2 | \(1\le n \le 100\) | \(1\le m \le 1000\) | - | \(\le 100\) |
| 3 | \(1\le n \le 1000\) | \(1\le m \le 10^4\) | 單個長度 \(\le 1000\) ,總長度 \(\le 10^5\) | \(\le 10^5\) |
| 4 | \(1\le n \le 1000\) | \(1\le m \le 10^4\) | 單個長度 \(\le 1000\) ,總長度 \(\le 10^5\) | \(\le 10^5\) |
| 5 | \(1\le n \le 10^4\) | \(1\le m \le 10^5\) | 總長度 \(\le 10^5\) | \(\le 10^5\) |
| 6 | \(1\le n \le 10^4\) | \(1\le m \le 10^5\) | 總長度 \(\le 10^5\) | \(\le 10^5\) |
| 7 | \(1\le n \le 10^4\) | \(1\le m \le 10^5\) | 總長度 \(\le 10^5\) | \(\le 10^5\) |
| 8 | \(1\le n \le 10^5\) | \(1\le m \le 10^5\) | - | \(\le 10^5\) |
| 9 | \(1\le n \le 10^5\) | \(1\le m \le 10^5\) | - | \(\le 10^5\) |
| 10 | \(1\le n \le 10^5\) | \(1\le m \le 10^5\) | - | \(\le 10^5\) |
Algorithm
\(Trie\) 圖,樹狀數組
Mentality
欲得正解,先想暴力。
思考一下暴力怎么做:建 \(Trie\) -> 建 \(fail\) -> 對于每對詢問 \(x,y\),由于 \(y\) 已經插入了 \(Trie\) 里,直接從 \(y\) 對應的終止結點往根走,并在每個點跳 \(fail\) 來計算答案。
當然,為了后面的分數,建 \(Trie\) 的時侯我們還是不能這么裸的,應該利用不同字符串間的高度重復性。由于打字機必需從末尾一個個刪除字母,我們可以維護一個指針 \(pos\) 指向上個字符串的結尾,每當運行一次刪除操作且此次操作會導致當前字符串與上一字符串的前綴重合長度 \(-1\) ,就令 \(pos\) 跳至父親結點處。當執行打印命令時,則從 \(pos\) 處開始建 \(Trie\) 即可。
不難知道,這樣我們的得分是 \(40\) 。
接下來,我們發現,\(fail\) 指針是構成了一棵樹的,那么我們本來對于詢問的暴力跳躍就可以得到轉化:在 \(x\) 的 \(fail\) 邊子樹內,有多少個結點屬于 \(y\) ?
那接下來就很明顯了:
先預處理出 \(fail\) 樹上的 \(dfn\) 序,然后離線詢問,將詢問掛載在 \(y\) 的終止結點上。
然后對 \(Trie\) 做 \(dfs\) ,每當進入一個結點就在對應的 \(fail\) 樹上的 \(dfn\) 序位置 \(+1\) ,當退出一個結點就 \(-1\) 。這樣一來,每當到達一個結點,就有且僅有根結點到當前結點路徑上的點對應位置 \(+1\) ,滿足了詢問的統計條件。
每到一個詢問點 \(x\) ,查詢 \([dfn[x],dfn[x]+size[x]-1]\) 區間內的 \(1\) 的個數即為答案。
Code
#include <algorithm> #include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <queue> #include <vector> using namespace std; void read(int &x) {x = 0;char ch = getchar();while (!isdigit(ch)) ch = getchar();while (isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();} } const int Max_n = 1e5 + 1, Max_m = 1e5 + 1, Max_len = 1e5 + 1, M = 26; int n, m, Ans[Max_n]; int top, pos, cnt_T, End[Max_n]; char S[Max_len], now_S[Max_len]; vector<int> que_d[Max_len], que_x[Max_len]; queue<int> q; void Trie_add(int x); struct Trie {int sur, ch[M], fa, deep, End;int nx(int x, int now) {if (!ch[x]) {ch[x] = ++cnt_T;Trie_add(now);}return ch[x];} } k[Max_len], tp[Max_len]; void Trie_add(int x) { k[cnt_T].fa = x, k[cnt_T].deep = k[x].deep + 1; } void Trie_build() {for (int i = 0; i < M; i++)if (k[0].ch[i]) q.push(k[0].ch[i]);while (!q.empty()) {int x = q.front();q.pop();for (int i = 0; i < M; i++)if (k[x].ch[i])k[k[x].ch[i]].sur = k[k[x].sur].ch[i], q.push(k[x].ch[i]);elsek[x].ch[i] = k[k[x].sur].ch[i];} } int cntr, head[Max_len], nx[Max_len], to[Max_len]; int cntd, d[Max_len], size[Max_len]; int cnts, s[Max_len << 1]; int c[Max_len]; bool vis[Max_len]; void get_s(int x) {s[++cnts] = x;for (int i = 0; i < M; i++)if (k[x].ch[i]) get_s(k[x].ch[i]);s[++cnts] = x; } void addr(int u, int v) {cntr++;to[cntr] = v, nx[cntr] = head[u];head[u] = cntr; } void Tree_build(int x) {size[x] = 1, d[x] = ++cntd;for (int i = head[x]; i; i = nx[i]) Tree_build(to[i]), size[x] += size[to[i]]; } void c_add(int k, int x) {if (!k) return;for (int i = k; i <= cnt_T + 1; i += i & -i) c[i] += x; } int c_query(int k) {int ans = 0;for (int i = k; i; i -= i & -i) ans += c[i];return ans; } void Ans_Count(int x) {for (int i = 1; i <= cnts; i++) {int x = s[i];if (!vis[x]) { // 第一次訪問代表進入結點vis[x] = 1, c_add(d[x], 1);for (int j = que_d[x].size() - 1; ~j; j--) {int now = que_x[x][j];Ans[que_d[x][j]] =c_query(d[now] + size[now] - 1) - c_query(d[now] - 1);}} else // 第二次訪問代表退出結點c_add(d[x], -1);} } int main() {scanf("%s", S);for (int i = 0, lim = strlen(S); i < lim; i++) {if (S[i] == 'B')top--, pos = k[pos].fa; // pos 的處理else if (S[i] == 'P') {for (int p = k[pos].deep + 1; p <= top; p++) // pos 的使用pos = k[pos].nx(now_S[p] - 'a', pos);k[pos].End = ++n;End[n] = pos;} elsenow_S[++top] = S[i];}get_s(0); // 先處理出 Trie 樹上點的訪問順序Trie_build(); // fail 指針構建read(m);int x, y;for (int i = 1; i <= m; i++) {read(x), read(y);que_d[End[y]].push_back(i);que_x[End[y]].push_back(End[x]); // 掛載詢問}for (int i = 1; i <= cnt_T; i++) addr(k[i].sur, i); // 連接 fail 邊Tree_build(0); // 構建 fail 樹Ans_Count(0); // 統計答案for (int i = 1; i <= m; i++) printf("%d\n", Ans[i]); }轉載于:https://www.cnblogs.com/luoshuitianyi/p/11054782.html
總結
以上是生活随笔為你收集整理的【NOI 2011】阿狸的打字机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在picture上显示透明Label
- 下一篇: ArcIMS 开发学习笔记(一)