题解:CF163E e-Government

Statement 解析

给了字符串集合 \(\{S\}\),有 \(N\) 次操作,另有与字符串集合 \(\{S\}\) 相同的 \(\{T\}\),每次给出一种操作:

  • 选择编号为 \(i\) 的字符串加入 \(\{T\}\),已存在于 \(\{T\}\) 中不执行。
  • \(\{T\}\) 中删除编号为 \(i\) 的字符串吗,不存在于 \(\{T\}\) 中不执行。
  • 询问,给定一个字符串 \(S'\),求当前集合 \(\{T\}\) 中每个串在 \(S'\) 中的出现次数之和。

Solution

看来是比较典的 AC 自动机的题目。

首先我们摸出一个 trick:求子串可以枚举前缀然后考虑这段前缀的后缀。

那么我们对集合 \(\{T\}\) 建 AC 自动机,那么当我们从根往下走的时候就是在枚举前缀(因为是 trie 树),然后处理 \(fail\) 指针,根据 \(fail\) 的含义:指向当前前缀的最长后缀的尾,我们可以通过跳 \(fail\) 得到这段前缀的后缀,每个点都有且仅有一个 \(fail\) 指针,那么从 \(fail_u \to u\) 建边得到的就是一棵外向树(有向且从根指向叶子),原本我们想要的是从 \(u\)\(fail_u\) 假设最后跳到 \(v\),那么在新建出来的树上 \(v\)\(u\) 子树内且从 \(fail_u\)\(u\) 子树内任意结点都是一种可行匹配,那么这就是个子树和问题。

但是这道题带删带修,其实再摸出一个 trick:子树内 \(dfn\) 连续,所以可以在 \(dfn\) 子树加一或者减一然后上传区间和,暴力单点查统计,线段树或者树状数组维护都可。

Code

#include <bits/stdc++.h>
//#define int long long
#define son(rt,x) Amt[rt].son[x]
#define fail(rt) Amt[rt].fail
#define lson(rt) (rt << 1)
#define rson(rt) (rt << 1 | 1)
#define len(rt) (Sgt[rt].r - Sgt[rt].l + 1)

using namespace std;
const int MAXN = 1e6 + 10;

int N, K, cnt, ind, idx;
int tail[MAXN], low[MAXN], dfn[MAXN], head[MAXN];
bool tag[MAXN];
string Style, Text;

struct Graphon {
	int to, nxt;
} E[MAXN];

struct AutoMaton {
	int son[26];
	int fail;
} Amt[MAXN];

struct Sgton {
	int l, r, sum, lazy;
} Sgt[MAXN << 2];

inline void Addedge (int u, int v) { E[idx].to = v, E[idx].nxt = head[u], head[u] = idx++; }

inline void InsertString (int ID) {
	int len = Style.length(), now = 0;
	for (int i = 0; i < len; i ++) {
		int chVal = Style[i] - 'a';
		if (!son(now, chVal))
			son(now, chVal) = ++cnt;
		now = son(now, chVal);
	}
	tail[ID] = now;
}

inline void GetFailPointer() {
	queue<int> q;
	for (int i = 0; i < 26; i ++) {
		if (son(0, i)) {
			q.emplace(son(0, i));
			Addedge (0, son(0, i));
		}
	}
	while (!q.empty()) {
		int now = q.front(); q.pop();
		for (int i = 0; i < 26; i ++) {
			if (!son(now, i))
				son(now, i) = son(fail(now), i);
			else {
				fail(son(now, i)) = son(fail(now), i);
				q.emplace (son(now, i));
				Addedge (fail(son(now, i)), son(now, i));
			}
		}
	}
}

void dfs (int from) {
	dfn[from] = ++ind;
	for (int i = head[from]; ~i; i = E[i].nxt) dfs (E[i].to);
	low[from] = ind;
}

void Build (int l, int r, int rt) {
	Sgt[rt].l = l, Sgt[rt].r = r;
	if (l == r) return;
	int mid = (l + r) >> 1;
	Build (l, mid, lson(rt));
	Build (mid + 1, r, rson(rt));
}

inline void pushdown (int rt) {
	if (Sgt[rt].lazy) {
		Sgt[lson(rt)].lazy += Sgt[rt].lazy;
		Sgt[rson(rt)].lazy += Sgt[rt].lazy;
		Sgt[lson(rt)].sum += len(lson(rt)) * Sgt[rt].lazy;
		Sgt[rson(rt)].sum += len(rson(rt)) * Sgt[rt].lazy;
		Sgt[rt].lazy = 0;
	}
}

void Modify (int ql, int qr, int rt, int val) {
	int l = Sgt[rt].l, r = Sgt[rt].r;
	if (ql <= l && qr >= r) {
		Sgt[rt].lazy += val;
		Sgt[rt].sum += len(rt) * val;
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(rt);
	if (ql <= mid)
		Modify (ql, qr, lson(rt), val);
	if (qr > mid)
		Modify (ql, qr, rson(rt), val);
	Sgt[rt].sum = Sgt[lson(rt)].sum + Sgt[rson(rt)].sum;
}

int query (int ql, int qr, int rt) {
	int l = Sgt[rt].l, r = Sgt[rt].r;
	if (ql <= l && qr >= r)
		return Sgt[rt].sum;
	int mid = (l + r) >> 1, res = 0;
	pushdown(rt);
	if (ql <= mid)
		res += query (ql, qr, lson(rt));
	if (qr > mid)
		res += query (ql, qr, rson(rt));
	return res;
}

inline int queryAns (string str) {
	int len = str.length(), now = 0, res = 0;
	for (int i = 1; i < len; i ++) {
		int chVal = str[i] - 'a';
		now = son(now, chVal);
		res += query (dfn[now], dfn[now], 1);
	}
	return res;
}

inline int chgStr (string str) {
	int len = str.length(), res = 0;
	for (int i = 1; i < len; i ++)
		res = res * 10 + str[i] - '0';
	return res;
}

signed main() {
	ios_base::sync_with_stdio (false);
	cin.tie (nullptr), cout.tie (nullptr);
	cin >> N >> K, cnt = ind = idx = 0;
	memset (head, -1, sizeof head);
	for (int i = 1; i <= K; i ++) 
		cin >> Style, InsertString(i);
	GetFailPointer(), dfs(0), Build (1, ind, 1);
	for (int i = 1; i <= K; i ++)
		tag[i] = true, Modify (dfn[tail[i]], low[tail[i]], 1, 1);
	for (int i = 1; i <= N; i ++) {
		cin >> Text;
		if (Text[0] == '+') {
			int strVal = chgStr(Text);
//			cout << strVal << "\n";
			if (!tag[strVal])
				tag[strVal] = true, Modify (dfn[tail[strVal]], low[tail[strVal]], 1, 1);
		} else if (Text[0] == '-') {
			int strVal = chgStr(Text);
//			cout << strVal << "\n";
			if (tag[strVal])
				tag[strVal] = false, Modify (dfn[tail[strVal]], low[tail[strVal]], 1, -1);
		} else {
			cout << queryAns(Text) << "\n";
		}
	}
	return 0;
}
posted @ 2025-04-27 19:41  xAlec  阅读(26)  评论(0)    收藏  举报