AC自动机

强制在线AC自动机,我们考虑二进制分组。

每插入一个串,把它建成一个 AC 自动机,放入栈中,然后当栈顶两 AC 自动机单词数相等时暴力合并两个自动机。

这样建出来 AC 自动机的尺寸就是当前总单词数的二进制表示,复杂度为 \(O(n\log n^2)\),但是带个26的常数,所以跑得比较慢。

不光是AC自动机,很多离线数据结构和算法强制在线,如果询问独立都可以这样搞。

#include <cstdio>
#include <queue>
#include <cstring>

inline int min(const int x, const int y) {return x < y ? x : y;}
inline int max(const int x, const int y) {return x > y ? x : y;}

struct word {
	std::vector<char> str;
	int val;
};
int ch[1000005][26], ch2[1000005][26], fail[1000005], now;
long long val[1000005], val2[1000005];
char essay[1000005], str[1000005];
struct Memory_Pool {
	int freememory[1000005], top, tot;
	inline int newmem() {return top ? freememory[top --] : ++ tot;}
	inline void delmem(int x) {
		memset(ch[x], 0, 26 << 2), memset(ch2[x], 0, 26 << 2);
		fail[x] = val[x] = val2[x] = 0;
		freememory[++ top] = x;
	}
} memory;

struct AC_automaton {
	int root, sze;
	word wd;
	std::queue<int> Q;
	std::vector<word> words;
	std::vector<int> nodes;
	inline void create() {nodes.clear(), words.clear(), sze = 0, nodes.push_back(root = memory.newmem());}
	void insert(int p, int cur) {
		if (cur == wd.str.size()) {val[p] += wd.val; return;}
		if (!ch[p][wd.str[cur] - 'a']) nodes.push_back(ch[p][wd.str[cur] - 'a'] = memory.newmem());
		insert(ch[p][wd.str[cur] - 'a'], cur + 1);
	}
	inline void add(const word x) {
		wd = x, ++ sze, words.push_back(x), insert(root, 0);
	}
	
	void build() {
		for (int i : nodes) memcpy(ch2[i], ch[i], 26 << 2), val2[i] = val[i];
		for (int i = 0; i < 26; ++ i)
			if (ch[root][i]) Q.push(ch[root][i]), fail[ch[root][i]] = root;
			else ch[root][i] = root;
		while (Q.size()) {
			int u = Q.front();
			Q.pop();
			for (int i = 0; i < 26; ++ i)
				if (ch[u][i]) fail[ch[u][i]] = ch[fail[u]][i], Q.push(ch[u][i]);
				else ch[u][i] = ch[fail[u]][i];
			val[u] += val[fail[u]];
		}
	}
	long long find(const char *essay, int len) const {
		long long ans = 0ll;
		for (int i = 0, p = root; i < len; ++ i) ans += val[p = ch[p][essay[i] - 'a']];
		return ans;
	}
} ac[21];

int main() {
	int n, type;
	long long ans = 0ll;
	scanf("%d%d", &n, &type);
	for (int i = 1; i <= n; ++ i) {
		int opt;
		scanf("%d", &opt);
		if (opt == 0) {
			word tmp;
			scanf("%s%d", str, &tmp.val);
			int len = strlen(str);
			for (int i = 0; i < len; ++ i) tmp.str.push_back(str[i]);
			if (type) for (int i = 0; i < len; ++ i) tmp.str[i] = 'a' + (tmp.str[i] - 'a' ^ ans) % 26;
			ac[++ now].create(), ac[now].add(tmp), ac[now].build();
			for (; now > 1 && ac[now].sze == ac[now - 1].sze; ac[-- now].build()) {
				for (int x : ac[now - 1].nodes) memcpy(ch[x], ch2[x], 26 << 2), val[x] = val2[x];
				for (word x : ac[now].words) ac[now - 1].add(x);
				for (int x : ac[now].nodes) memory.delmem(x);
			}
		} else {
			scanf("%s", essay);
			int len = strlen(essay);
			if (type) for (int i = 0; i < len; ++ i) essay[i] = 'a' + (essay[i] - 'a' ^ ans) % 26;
			ans = 0ll;
			for (int i = 1; i <= now; ++ i) ans += ac[i].find(essay, len);
			printf("%lld\n", ans);
		}
	}
	return 0;
}

Divljak

建立 fail 树过后,添加相当于把若干点和树根构成的树上所有点加 \(1\),查询就是单点查询。

所以虚树+树状数组维护即可(当然这题不用建虚树,只需要用到虚树的某个结论)。

#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++)

int len;
char buf[100000], str[3000005], *p1, *p2;
inline int read() {char ch; int x = 0; while ((ch = gc) < 48); do x = x * 10 + ch - 48; while ((ch = gc) >= 48); return x;}
inline void readstr() {len = 0; char ch; while ((ch = gc) < 'a'); do str[len ++] = ch; while ((ch = gc) >= 'a');}

struct Edge {int to, nxt;} e[3000005];
int ch[3000005][26], head[3000005], fail[3000005], ID[3000005], dep[3000005], root, tot, cnt;
int In[3000005], Out[3000005], c[3000005], a[3000005], fa[3000005][22], belong[3000005];
struct cmp {inline bool operator () (const int x, const int y) const {return In[x] < In[y];}};
std::queue<int> Q;
inline void AddEdge(int u, int v) {e[++ tot].to = v, e[tot].nxt = head[u], head[u] = tot;}
inline void update(int x, int d) {for (int i = x; i <= 2000000; i += (i & ~i + 1)) c[i] += d;}
inline int query(int x) {int sum = 0; for (int i = x; i; i &= i - 1) sum += c[i]; return sum;}
void insert(int p, int cur, int id) {
	if (cur == len) {belong[id] = p; return;}
	if (!ch[p][str[cur] - 'a']) ch[p][str[cur] - 'a'] = ++ tot;
	insert(ch[p][str[cur] - 'a'], cur + 1, id);
}

void build() {
	for (int i = 0; i < 26; ++ i) if (ch[root][i]) Q.push(ch[root][i]);
	while (Q.size()) {
		int u = Q.front(); Q.pop(); AddEdge(fail[u], u), dep[u] = dep[fail[u]] + 1;
		for (int i = 0; i < 26; ++ i)
			if (ch[u][i]) fail[ch[u][i]] = ch[fail[u]][i], Q.push(ch[u][i]);
			else ch[u][i] = ch[fail[u]][i];
	}
}
void dfs(int u) {
	In[u] = ++ cnt; for (int i = 1; i <= 20; ++ i) fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for (int i = head[u]; i; i = e[i].nxt) fa[e[i].to][0] = u, dfs(e[i].to); Out[u] = cnt;
}
int LCA(int u, int v) {
	if (dep[u] < dep[v]) u ^= v ^= u ^= v; int tmp = dep[u] - dep[v];
	for (int i = 0; i <= 20; ++ i) if (tmp & 1 << i) u = fa[u][i]; if (u == v) return u;
	for (int i = 20; i >= 0; -- i) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}

int main() {
	int n = read(), q;
	for (int i = 1; i <= n; ++ i) readstr(), insert(root, 0, i);
	build(), dfs(0), q = read();
	while (q --) {
		int opt = read(), x;
		if (opt == 1) {
			readstr(), cnt = 0;
			for (int i = 0, p = root; i < len; ++ i) a[++ cnt] = p = ch[p][str[i] - 'a'];
			std::sort(a + 1, a + cnt + 1), cnt = std::unique(a + 1, a + cnt + 1) - a - 1;
			std::sort(a + 1, a + cnt + 1, cmp());
			for (int i = 1; i <= cnt; ++ i) update(In[a[i]], 1);
			for (int i = 1; i < cnt; ++ i) update(In[LCA(a[i], a[i + 1])], -1);
		} else x = read(), printf("%d\n", query(Out[belong[x]]) - query(In[belong[x]] - 1));
	}
	return 0;
}
posted @ 2022-03-16 19:26  zqs2020  阅读(51)  评论(0编辑  收藏  举报