1. 程式人生 > >[Luogu P2414] [BZOJ 2434] [NOI2011]阿狸的打字機

[Luogu P2414] [BZOJ 2434] [NOI2011]阿狸的打字機

洛谷傳送門

BZOJ傳送門

題目背景

阿狸喜歡收藏各種稀奇古怪的東西,最近他淘到一臺老式的打字機。

題目描述

打字機上只有 28 28 個按鍵,分別印有 26 26 個小寫英文字母和 B

B P P 兩個字母。經阿狸研究發現,這個打字機是這樣工作的:

  • 輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最後)。
  • 按一下印有 B B
    的按鍵,打字機凹槽中最後一個字母會消失。
  • 按一下印有 P P 的按鍵,打字機會在紙上打印出凹槽中現有的所有字母並換行,但凹槽中的字母不會消失。

例如,阿狸輸入aPaPBbP,紙上被列印的字元如下:

a aa ab 我們把紙上打印出來的字串從 1

1 開始順序編號,一直到 n n 。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數 ( x , y ) (x,y) (其中 1 x , y n 1≤x,y≤n ),打字機會顯示第 x x 個列印的字串在第 y y 個列印的字串中出現了多少次。

阿狸發現了這個功能以後很興奮,他想寫個程式完成同樣的功能,你能幫助他麼?

輸入輸出格式

輸入格式:

輸入的第一行包含一個字串,按阿狸的輸入順序給出所有阿狸輸入的字元。

第二行包含一個整數 m m ,表示詢問個數。

接下來 m m 行描述所有由小鍵盤輸入的詢問。其中第 i i 行包含兩個整數 x , y x, y ,表示第 i i 個詢問為 ( x , y ) (x, y)

輸出格式:

輸出 m m 行,其中第i行包含一個整數,表示第i個詢問的答案。

輸入輸出樣例

輸入樣例#1:

aPaPBbP
3
1 2
1 3
2 3

輸出樣例#1:

2
1
0

說明

資料範圍:

對於 100 % 100\% 的資料, n 100000 , m 100000 n\le 100000,m\le 100000 ,第一行總長度 100000 \le 100000

img

解題分析

A C AC 自動機好題。 建自動機什麼的都好說, 只是額外維護一個父親節點表示從其轉移過來的即可完成回退操作。

問題在於如果我們每次暴力匹配複雜度會變成 O ( N 2 ) O(N^2) 的。 如何優化?

考慮我們是如何暴力的: 對 x x 串建立 A C AC 自動機, 將 y y 串依次插入, 暴力跳 f a i l fail , 看是否能到達 x x 串的終止位置。 我們發現, 只要插入的字元在 f a i l fail 樹上在 x x 串終止位置的子樹中, 就會產生貢獻。

這樣就好做了:我們離線詢問, D F S DFS 整顆 t r i e trie 樹, 每到達一個節點就將其在 f a i l fail 樹上的 d f s dfs 序處的貢獻 + 1 +1 。 如果到達了某個串的終止節點, 就回答其作為 y y 串的所有詢問(直接查詢 x x f a i l fail 樹中的子樹貢獻之和)。

程式碼如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <queue>
#include <vector>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define lbt(i) ((i) & (-(i)))
#define MX 100500
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc);
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
}
int cnt, m, tot, dfn, root;
int son[MX][26], fat[MX], fail[MX], ed[MX], lb[MX], rb[MX], head[MX], tree[MX], p[MX], ans[MX];
char buf[MX];
struct INFO {int tar, tim;};
std::vector <int> to[MX];
std::vector <INFO> que[MX];
std::queue <int> q;
IN void add(R int pos, R int del) {for (; pos <= cnt + 1; pos += lbt(pos)) tree[pos] += del;}
IN int query(R int pos) {int ret = 0; for (; pos; pos -= lbt(pos)) ret += tree[pos]; return ret;}
IN void insert(char *str)
{
	R int now = root, len = std::strlen(str), id;
	for (R int i = 0; i < len; ++i)
	{
		if (str[i] == 'P') ed[now] = ++tot, p[tot] = now;
		else if (str[i] == 'B') now = fat[now];
		else
		{
			id = str[i] - 'a';
			if (!son[now][id]) son[now][id] = ++cnt, fat[son[now][id]] = now;
			now = son[now][id];
		}
	}
}
void build()
{
	R int now, cur; fail[0] = -1;
	for (R int i = 0; i < 26; ++i) if (son[root][i]) q.push(son[root][i]), to[root].push_back(son[root][i]);
	W (!q.empty())
	{
		now = q.front(); q.pop();
		for (R int i = 0; i < 26; ++i)
		{
			if (son[now][i])
			{
				cur = fail[now];
				W ((~fail[cur]) && (!son[cur][i])) cur = fail[cur];
				fail[son[now][i]] = son[cur][i];
				q.push(son[now][i]); to[fail[son[now][i]]].push_back(son[now][i]);
			}
		}
	}
}
void DFS1(R int now)
{
	lb[now] = ++dfn;
	for (R int i = to[now].size() - 1; ~i; --i) DFS1(to[now][i]);
	rb[now] = dfn;
}
void DFS2(R int now)
{
	add(lb[now], 1);
	if (ed[now])
	{
		INFO cur;
		for (R int i = que[now].size() - 1; ~i; --i)
		{
			cur = que[now][i];
			ans[cur.tim] = query(rb[p[cur.tar]]) - query(lb[p[cur.tar]] - 1);
		}
	}
	for (R int i = 0; i < 26; ++i)
	if (son[now][i]) DFS2(son[now][i]);
	add(lb[now], -1);
}
int main(void)
{
	int x, y;
	scanf("%s", buf); insert(buf);
	in(m);
	for (R int i = 1; i <= m; ++i)
	{
		in(x), in(y);
		que[p[y]].push_back({x, i});
	}
	build(); DFS1(0); DFS2(0);
	for (R int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
}