bzoj-2434阿狸的打字机[AC自动机+fail树+dfs序+树状数组]

前置知识:

	ac自动机	
	fail树

关键在于fail树的应用:子节点的后缀一定包含父节点。
对于x在y中出现了多少次。
先假设y至少包含一个x。

那么在trie图中从根节点到y的那条链中一定存在某个节点的后缀是x。

所以找到这样的节点就能知道包含x的数量。
处理:
我们可以按照打字的顺序建图,记录一下tire图中的pre,当操作’B’的时候就跳到pre[u],对于’P’的操作记录一下时间戳,对应询问操作。否则就直接插入。
fail指针构建完后就可以构建fail树了,然后记录一下每个节点的子树范围。
然后我们就可以用刚才处理的信息来搞事情了。

别忘了 我们现在的问题是如何统计在y的链中并且后缀是x的节点。

记录答案:
可以把询问离线下来,一边处理字符串一边记录答案。
而我们想要的就是当前时间戳下fail树中x的子节点中有哪些节点是存在的。
可以用树状数组维护,插入字符时就把对应fail树的编号插入到树状数组里面,'B’操作我们就在树状数组中取消对应的编号,然后遇到时间戳,就看一下有没有当前时间戳的询问,树状数组查一下x的子节点区间里面的存在的个数。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
char ss[maxn];
struct AC{
	int nex[maxn][26], tot, root;
	int f[maxn], ed[maxn], pre[maxn], date[maxn];
	vector<int>G[maxn];
	int newnode() {
		for(int i = 0; i < 26; i++)
			nex[tot][i] = -1;
		ed[tot] = 0;
		return tot++;
	}
	void init() {
		tot = 0;root = newnode();
	}
	void build() {
		int len = strlen(ss), u = root, cnt = 0;
		for(int i = 0; i < len; i++) {
			if(ss[i] == 'P') date[++cnt] = u;
			else if(ss[i] == 'B') u = pre[u];
			else {
				int ch = ss[i]-'a';
				if(nex[u][ch] == -1) {
					pre[nex[u][ch] = newnode()] = u;
				}
				u = nex[u][ch];
			}
		}
	}
	void getfail() {
		queue<int>Q;
		for(int i = 0; i < 26; i++) {
			if(nex[root][i] == -1) nex[root][i] = root;
			else {
				f[nex[root][i]] = root;
				Q.push(nex[root][i]);
			}
		}
		while(!Q.empty()) {
			int u = Q.front();Q.pop();
			for(int i = 0; i < 26; i++) {
				if(nex[u][i] == -1) nex[u][i] = nex[f[u]][i];
				else {
					f[nex[u][i]] = nex[f[u]][i];
					Q.push(nex[u][i]);
				}
			}
		}
		for(int i = 1; i <= tot; i++) {
			G[i].push_back(f[i]);
			G[f[i]].push_back(i);
		}
	}
}ac;
int idx = 0, in[maxn], out[maxn], res[maxn];
void dfs(int u, int f) {
	in[u] = ++idx;
	for(int i = 0; i < ac.G[u].size(); i++) {
		int v = ac.G[u][i];
		if(v == f) continue;
		dfs(v, u);
	}
	out[u] = idx;
}
class bit_tree{
private:
	int tr[maxn], n;
public:
	bit_tree(int n) {
		this->n = n;
	}
	int lowerbit(int x) {
		return ((x)&(-x));
	}
	void update(int pos, int val) {
		while(pos <= n) {
			tr[pos] += val;
			pos += lowerbit(pos);
		}
	}
	int query(int pos) {
		int res = 0;
		while(pos > 0) {
			res += tr[pos];
			pos -= lowerbit(pos);
		}
		return res;
	}
};
struct node{
	int x, id;
};
vector<node>ve[maxn];
void solve() {
	int n;
	scanf("%s", ss);
	ac.init();
	ac.build();
	ac.getfail();
	dfs(0, -1);
	bit_tree t(idx);
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) {
		int x, y;scanf("%d%d", &x, &y);
		ve[y].push_back(node{x, i});
	}
	int u = ac.root, pos = 0, len = strlen(ss);
	for(int i = 0; i < len; i++) {
		if(ss[i] == 'B') {
			t.update(in[u], -1);
			u = ac.pre[u];
		}
		else if(ss[i] == 'P') {
			pos++;int sz = ve[pos].size();
			for(int j = 0; j < sz; j++) {
				int v = ac.date[ve[pos][j].x], k = ve[pos][j].id;
				res[k] = t.query(out[v])-t.query(in[v]-1);
			}
		}
		else {
			u = ac.nex[u][ss[i]-'a'];
			t.update(in[u], 1);
		}
	}
	for(int i = 1; i <= n; i++) {
		printf("%d\n", res[i]);
	}
}
int main() {
	solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值