阿狸喜歡收藏各類稀奇古怪的東西,最近他淘到一臺老式的打字機。打字機上只有28個按鍵,分別印有26個小寫英文字母和'B'、'P'兩個字母。
經阿狸研究發現,這個打字機是這樣工做的:
l 輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最後)。
l 按一下印有'B'的按鍵,打字機凹槽中最後一個字母會消失。
l 按一下印有'P'的按鍵,打字機會在紙上打印出凹槽中現有的全部字母並換行,但凹槽中的字母不會消失。
例如,阿狸輸入aPaPBbP,紙上被打印的字符以下:
a
aa
ab
咱們把紙上打印出來的字符串從1開始順序編號,一直到n。打字機有一個很是有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數(x,y)(其中1≤x,y≤n),打字機會顯示第x個打印的字符串在第y個打印的字符串中出現了多少次。
阿狸發現了這個功能之後很興奮,他想寫個程序完成一樣的功能,你能幫助他麼?php
輸入的第一行包含一個字符串,按阿狸的輸入順序給出全部阿狸輸入的字符。
第二行包含一個整數m,表示詢問個數
接下來m行描述全部由小鍵盤輸入的詢問。其中第i行包含兩個整數x, y,表示第i個詢問爲(x, y)。數組
輸出m行,其中第i行包含一個整數,表示第i個詢問的答案。ui
Triecode
很是不錯的一道題目blog
首先暴力把每一個字符串扔到trie樹裏並打上標記,$O(M)$排序
咱們查詢的是第$x$個字符串在第$y$個字符串中出現了多少次,若是一次一次的查確定是太浪費了。ip
考慮能不能一次多查幾個,若是你作過這道題的話確定能想到是AC自動機。字符串
這樣咱們對於相同的$y$,僅作一次查詢就好了。爲了更方便的查找,咱們對$y$進行排序,這樣就能夠$O(1)$的維護出$y$的形態get
回到上一個問題,考慮如何查詢出現次數,
根據$fail$樹的性質,咱們不難發現,若$x$節點在$root$到$y$任意一個節點的$fail$樹上,那麼$x$必定就是$y$的子串
(這裏簡單證實一下:判斷一個串是不是另外一個串的子串能夠轉換爲判斷這個串是不是另外一個串前綴的後綴,咱們枚舉從根到$y$的路徑,實際上就是枚舉了$y$的前綴,而在$fail$樹上查找,實際上就是在枚舉$y$的後綴)
這樣查詢一次的複雜度爲$O(siz)$,若全部的$y$全不相同確定仍是會涼涼
接下來就是神仙操做了!
考慮轉化問題,
若$x$節點在$root$到$y$任意一個節點的$fail$樹上,那麼$x$必定就是$y$的子串
同理,若$y$在$x$的子樹中,那麼$x$必定是$y$的子串
這樣咱們在枚舉$y$的時候,能夠在通過的路徑上打上$+1$的標記(退出的時候打上$-1$的標記),當遇到當前節點爲$y$時,查詢一下$x$的子樹的和就行了
這樣的複雜度仍然沒有降下來爲,$O(siz^2)$
可是!別忘了在樹上有一種經典的操做—>dfs序+樹狀數組
而後這題就$O(siz \log siz)$的作完了
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN = 1e5 + 10;
inline int read() {
char c = getchar(); int x = 0, f = 1;
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
const int root = 0, B = 26;
int Num, N;
char s[MAXN];
struct Query {
int x, y, ID, ans;
bool operator < (const Query &rhs) const {
return y == rhs.y ? x < rhs.x : y < rhs.y;
}
}Q[MAXN];
vector<int> v[MAXN];
int ch[MAXN][26], fa[MAXN], fail[MAXN], val[MAXN], siz[MAXN], tot = 0, Pos[MAXN], meiyong = 0;
void Build() {
int now = root;
for(int i = 1; i <= N; i++) {
int x = s[i] - 'a';
if(x == -31) {now = fa[now]; continue;}
if(x == -17) {Pos[++meiyong] = now; continue;}
if(!ch[now][x]) ch[now][x] = ++tot;
fa[ch[now][x]] = now; now = ch[now][x];
}
}
void GetFail() {
queue<int> q;
for(int i = 0; i < B; i++) if(ch[root][i]) q.push(ch[root][i]);
while(!q.empty()) {
int p = q.front(); q.pop();
for(int i = 0; i < B; i++)
if(!ch[p][i]) ch[p][i] = ch[fail[p]][i];
else fail[ch[p][i]] = ch[fail[p]][i], q.push(ch[p][i]);
v[fail[p]].push_back(p);
}
}
int ID[MAXN], cnt; //ID[i]表示第i個節點在fail樹上的編號
void dfs(int x) {
ID[x] = ++cnt; siz[ID[x]] = 1;
for(int i = 0; i < v[x].size(); i++)
dfs(v[x][i]), siz[ID[x]] += siz[ID[v[x][i]]];
}
struct BIT {
#define lb(x) (x & (-x))
int Tree[MAXN];
int add(int pos, int val) {
for(int i = pos; i <= cnt; i += lb(i))
Tree[i] += val;
}
int sum(int pos) {
int ans = 0;
for(int i = pos; i >= 1; i -= lb(i))
ans += Tree[i];
return ans;
}
int QUERY(int x, int y) {
return sum(y) - sum(x - 1);
}
}T;
void Solve() {
int now = root, cur = 1, world = 0;
for(int i = 1; i <= N; i++) {
int x = s[i] - 'a';
if(x == -31) {T.add(ID[now], -1), now = fa[now]; continue;}
else if(x == -17) {
world++;
for(int x; Q[cur].y == world; cur++)
x = Pos[Q[cur].x], Q[cur].ans = T.QUERY(ID[x], ID[x] + siz[ID[x]] - 1);
}
else now = ch[now][x], T.add(ID[now], +1);
}
}
bool comp(const Query &a, const Query &b) {
return a.ID < b.ID;
}
int main() {
#ifdef WIN32
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
#endif
scanf("%s", s + 1); N = strlen(s + 1);
Build();
GetFail();
dfs(root);
Num = read();
for(int i = 1; i <= Num; i++) {
int x = read(), y = read();
Q[i] = (Query) {x, y, i};
}
sort(Q + 1, Q + Num + 1);
Solve();
sort(Q + 1, Q + Num + 1, comp);
for(int i = 1; i <= Num; i++)
printf("%d\n", Q[i].ans);
return 0;
}