日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【NOI 2011】阿狸的打字机

發布時間:2025/3/20 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【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 3

Output

2 1 0

Range

所有測試數據的范圍和特點如下表所示:

測試點編號\(n\) 的規模\(m\) 的規模字符串長度輸入總長 (輸入文件第一行的字符數)
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】阿狸的打字机的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。