【XR-4】文本编辑器

直接做是困难的,不妨依照部分分来思考。

- Subtask 3

首先会进入一个误区:维护修改,通过循环串的性质在 \(\tt KMP\) 自动机上优化遍历。

但可以发现这样很难处理,我们不妨 直接维护 每个位置的答案。

令唯一的模式串长度为 \(d\)\(f_i\) 为文本串 \([\max(i - d + 1, 1), i]\) 与模式串是否匹配。

查询直接求 \([L + d - 1, R]\) 的区间和即可。

考虑一次修改对 \(f\) 的影响,显然仅会修改 \([L, R + d - 1]\) 中的 \(f\)

并且,我们 直接在序列上观察 可以发现:

  • 修改后的 \(f\) 会从 \(L + d\) 开始呈长度为 \(|t|\) 的周期性变化。

由于将区间修改为周期变化的字符串,那么与从 \(L + d\) 开始与模式串的最长 \(border\) 每隔 \(|t|\) 个位置均相同。

则可知从 \(L + d\) 开始的串在 \(\tt KMP\) 自动机上成周期性的遍历,故 \(f\) 也从此位置开始呈长度为 \(|t|\) 的周期性变化。

这意味着我们只需要在 \(\tt KMP\) 自动机上暴力遍历 \(|t|\) 个节点即可求得 \([L + d, R]\) 这一段在修改后的 \(f\) 序列。

但需要注意的是,此时我们假定可以快速得到修改后的序列 \(S_{1, L + d - 1}\)\(\tt KMP\) 自动机上遍历到的节点。

我们将求解这个节点的做法称为「待解决的问题 \(1\)」。

对此,我们本质上只需要支持:

    • 给定 \(l, r\) 和一段序列 \(t\),将 \(l \sim r\) 替换为 \(t\) 反复出现的结果。(若最后一段并非完整周期,则将非最后一段和最后一段看作两个修改)
    • 给定 \(l, r\),区间查询序列的和。

这两个操作可以简单的使用线段树维护:

对于每一次修改,我们记录修改的序列元素,前缀和,后缀和,以及整体和。

对于线段树上每个节点,我们维护该区间的和 \(sum\),懒标记(当且仅当这个区间被某次修改覆盖时存在):当前被第 \(t\) 次操作覆盖,左边散块开始于 \(t\) 序列中的 \(l\),右边散块结束与 \(t\) 序列的 \(r\),中间整块的数量 \(num\)

打懒标记,懒标记下传,\(\tt pushup\) 都是容易的。

由此我们以 \(\mathcal{O(\sum |t| + q \log n)}\) 的优秀复杂度解决了 \([L + d, R]\) 的修改。

考虑完 \([L + d, R]\) 这一段的修改,接下来考虑 \([L, L + d - 1]\) 这一段的修改。

注意到 \(d\) 很小,于是可以在 \(S_{1, L - 1}\)\(\tt KMP\) 自动机上的节点开始往下直接遍历。

一样需要注意的是,此时我们假定可以快速得到 \(S_{1, L - 1}\)\(\tt KMP\) 自动机上的节点。

我们将求解这个节点的做法称为「待解决的问题 \(2\)」。

此时我们惊喜地发现,由于我们往后暴力遍历到了 \(S_{1, L + d - 1}\),由此我们解决了「待解决的问题 \(1\)」。

\([R + 1, R + d - 1]\) 的修改与 \([L, L + d - 1]\) 的修改操作是类似的(有一点差别,请自行解决),因此下面只考虑后者的修改。

但现在存在一个问题,我们可以 \(\mathcal{O(d)}\) 获得 \([L, L + d - 1]\) 修改后的 \(f\) 序列,但若要将其在线段树上修改,复杂度看上去将会是 \(\mathcal{O(d \log n)}\) 的,不太行。

事实上,如果我们直接一次修改暴力遍历线段树至叶子节点,其复杂度其实是 \(\mathcal{O(d + \log n)}\)

我们找到区间 \([L, L + d - 1]\) 在线段树上定位的 \(\log n\) 个区间,这里的复杂度是 \(\mathcal{O(\log n)}\) 的。

而接下来遍历的所有节点,实质上是这 \(\log n\) 个区间下面的所有节点。

又线段树的大小是线性的,因此这部分的节点数为 \(\mathcal{O(d)}\)

至此,我们花费了 \(\mathcal{O}(\sum |t| + q(\log n + d))\) 的花费将这个问题转化为解决:「待解决的问题 \(2\)

由一开始的观察可知,\(A\) 序列在 \(\tt KMP\) 自动机上遍历得到的节点序列修改后与 \(f\)一模一样 的周期性。

由此我们使用维护 \(f\) 的方法来维护 \(A\) 序列在 \(\tt KMP\) 自动机上遍历得到的节点序列 \(z\),复杂度与 \(f\) 的维护一致。

至此,我们以 \(\mathcal{O(|\Sigma| \sum|s_i| + \sum |t| + q(\log n + d))}\) 的复杂度解决了这个子问题。

- Subtask 4

同样考虑直接维护每个节点的答案,但由于这里为多模式串,因此需要改变定义。

\(f_i\)\(A\) 中以 \(i\) 结尾的子串与所有模式串的匹配次数。

\(g_{i, j}\)\(A\) 中以 \(j\) 结尾的子串与长度不超过 \(j\) 的模式串匹配的次数。

初始信息我们直接维护出 \(fail\) 树上每个节点的答案,用 \(A\)\(\tt ACAM\) 上直接遍历并继承 \(fail\) 树上的答案即可。

预处理复杂度是 \(\mathcal{O}(\sum |s|(d + |\Sigma|) + nd)\) 的。

一次查询的答案显然为:

\[\sum\limits_{i = L} ^ {L + d - 1} g_{i - L + 1, i} + \sum\limits_{i = L + d} ^ R f_i \]

对于前半部分,我们直接暴力,单次复杂度 \(\mathcal{O(d)}\),后半部分我们前缀和查询。故复杂度瓶颈在于预处理。

- Subtask 5 \(\sim\) 7

考虑维护 \(Subtask 4\) 中的两个值,查询也使用同样的方式。

虽然加入了多模式串,但我们发现 \(f\) 修改的周期性依然存在,因此 \(f\) 是容易维护的(节点序列 \(z\) 也可以一样的维护)。

又我们维护了节点序列 \(z\),因此我们在计算 \(g\) 的贡献时可以先取出 \([L, L + d - 1]\) 的节点序列 \(z\),然后直接暴力调用 \(\tt ACAM\) 上预处理的每个节点的答案即可。

复杂度 \(\mathcal{O}(\sum |s|(d + |\Sigma|) + \sum |t| + q(\log n + d))\)

毒瘤题,代码写了一晚上

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define dep(i, l, r) for (int i = r; i >= l; --i)
const int N = 3e5 + 5;
const int M = 1e6 + 5;
const int K = 60 + 5;
struct tree { int l, r, t, num, sum; } ;
vector <int> U[N], pre[N], suf[N];
// U[i][0] 为第 i 次修改的长度,接下来为修改序列
// pre[i], suf[i] 分别为第 i 次修改序列的前缀 / 后缀和 
char s[M], t[M];
int n, m, q, z, l, r, x, opt, ans, totU, a[M], b[M], c[M];
// b 为用于暴力区间线段树修改的中转数组 

struct ST {
	#define ls (p << 1)
	#define rs (p << 1 | 1)
	#define mid ((l + r) >> 1)
	tree t[M << 2];
	void build (int p, int l, int r) {
		t[p].t = -1, t[p].num = t[p].sum = 0;
		if(l == r) { t[p].sum = a[l]; return ; }
		build(ls, l, mid), build(rs, mid + 1, r);
		t[p].sum = t[ls].sum + t[rs].sum;
	}
	int gi (int x, int l, int r, tree k) {
		if(x < l) return k.l;
		if(x > r) return k.r;
		int len = U[k.t][0];
		if(x - l + 1 <= len - k.l + 1) x = k.l + x - l;
		else x = (x - l - len + k.l - 1) % len + 1;
		return x;
	}
	// 求序列中 x 这个位置在修改序列中的位置 
	tree Get(int l, int r, int ul, int ur, tree k) {
		int len = U[k.t][0], id1, id2, sum, nL, nR;
		
		if(l <= ul) id1 = 0;
		else {
			if(l - ul + 1 <= len - k.l + 1) id1 = 0;
			else id1 = ceil(1.0 * (l - ul - len + k.l) / len);
		}
		if(r - ul + 1 <= len - k.l + 1) id2 = 0;
		else id2 = ceil(1.0 * (r - ul - len + k.l) / len);
		
		nL = gi(l, ul, ur, k), nR = gi(r, ul, ur, k);
		sum = pre[k.t][len] * (id2 - id1 - 1);
		sum += suf[k.t][nL] + pre[k.t][nR];
		
		return (tree){nL, nR, k.t, id2 - id1 - 1, sum};
	} 
	void down (int p, int l, int r) {
		if(t[p].t == -1) return ;
		t[ls] = Get(l, mid, l, r, t[p]), t[rs] = Get(mid + 1, r, l, r, t[p]);
		t[p].t = -1;
	}
	void update1 (int p, int l, int r, int x, int y, tree k) {
		if(x > y || y < l || x > r) return ;
		if(l >= x && r <= y) { t[p] = k; return ; }
		down(p, l, r);
		if(mid >= x) update1(ls, l, mid, x, min(y, mid), Get(l, mid, x, y, k));
		if(mid < y) update1(rs, mid + 1, r, max(x, mid + 1), y, Get(mid + 1, r, x, y, k));
		t[p].sum = t[ls].sum + t[rs].sum; 
	}
	// 支持区间覆盖 
	void update2 (int p, int l, int r, int x, int y) {
		if(x > y || y < l || x > r) return ;
		if(l == r) { t[p].sum = b[l]; return ; }
		down(p, l, r); 
		if(mid >= x) update2(ls, l, mid, x, y);
		if(mid < y) update2(rs, mid + 1, r, x, y);
		t[p].sum = t[ls].sum + t[rs].sum;
	}
	// 支持线段树暴力区间单点修改,中转数组为 b 
	int query (int p, int l, int r, int x, int y) {
		if(x > y || y < l || x > r) return 0;
		if(l >= x && r <= y) return t[p].sum;
		down(p, l, r);
		int ans = 0;
		if(mid >= x) ans += query(ls, l, mid, x, y);
		if(mid < y) ans += query(rs, mid + 1, r, x, y);
		return ans;
	}
	void Get (int p, int l, int r, int x, int y) {
		if(x > y || y < l || x > r) return ;
		if(l == r) { c[l] = t[p].sum; return ; }
		down(p, l, r);
		if(mid >= x) Get(ls, l, mid, x, y);
		if(mid < y) Get(rs, mid + 1, r, x, y);
	}
	// 支持线段树暴力区间取出,中转数组为 c 
} T[3];

namespace ACAM {
	#define Next(i, u) for (int i = h[u]; i; i = e[i].next)
	struct edge { int v, next; } e[N << 1];
	int cnt, tot, num, h[N], tr[N], fail[N], g[N][K], ch[N][K];
	void reset () {
		rep(i, 0, cnt) {
			fail[i] = 0;
			rep(j, 0, 62) ch[i][j] = g[i][j] = 0;
		}
		rep(i, 0, 25) tr['a' + i] = ++num;
		rep(i, 0, 25) tr['A' + i] = ++num;
		rep(i, 0, 9) tr['0' + i] = ++num;
		cnt = 0;
	}
	void insert (int n, char s[]) {
		int x = 0;
		rep(i, 1, n) {
			if(!ch[x][tr[s[i] - 0]]) ch[x][tr[s[i] - 0]] = ++cnt;
			x = ch[x][tr[s[i] - 0]];
		}
		++g[x][n];
	}
	void add (int u, int v) {
		e[++tot].v = v, e[tot].next = h[u], h[u] = tot;
		e[++tot].v = u, e[tot].next = h[v], h[v] = tot;
	}
	void dfs (int u, int fa) {
		rep(i, 1, 50) g[u][i] += g[fa][i];
		Next(i, u) if(e[i].v != fa) dfs(e[i].v, u);
	}
	void build () {
		queue <int> Q;
		rep(i, 1, 62) if(ch[0][i]) Q.push(ch[0][i]);
		while (!Q.empty()) {
			int u = Q.front(); Q.pop();
			rep(i, 1, 62) {
				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];
			}
		}
		rep(i, 1, cnt) add(fail[i], i);
		dfs(0, -1);
		rep(i, 1, cnt) rep(j, 1, 50) g[i][j] += g[i][j - 1];
	}
} 
using namespace ACAM;

void Modify (int o, int l, int r, int m, int *a) {
	if(l > r) return ;
	++totU, U[totU].push_back(m);
	rep(i, 1, m) U[totU].push_back(a[i]);
	
	pre[totU].push_back(0), suf[totU].push_back(0);
	rep(i, 1, m) pre[totU].push_back(pre[totU][i - 1] + U[totU][i]);
	rep(i, 1, m) suf[totU].push_back(0);
	suf[totU][m] = U[totU][m];
	dep(i, 1, m - 1) suf[totU][i] = suf[totU][i + 1] + U[totU][i];
	
	T[o].update1(1, 1, n, l, r, (tree){1, (r - l) % m + 1, totU, l == r ? -1 : (int)ceil(1.0 * (r - l - 1) / m), 0});
} 

signed main () {
	scanf("%lld%lld%lld%s", &n, &m, &q, s + 1);
	
	reset();
	rep(i, 1, m) scanf("%s", t + 1), l = strlen(t + 1), insert(l, t);
	build();
	
	x = 0;
	rep(i, 1, n) x = ch[x][tr[s[i] - 0]], a[i] = g[x][50];
	T[0].build(1, 1, n);
	x = 0;
	rep(i, 1, n) x = ch[x][tr[s[i] - 0]], a[i] = x;
	T[1].build(1, 1, n);
	rep(i, 1, n) a[i] = s[i];
	T[2].build(1, 1, n);
	
	while (q--) {
		scanf("%lld%lld%lld", &opt, &l, &r);
		if(opt == 2) {
			scanf("%s", t + 1), m = strlen(t + 1);
			rep(i, 1, m) a[i] = t[i];
			Modify(2, l, r, m, a);
			
			int cur = T[1].query(1, 1, n, l - 1, l - 1);
			rep(i, l, min(l + 49, r)) 
				cur = ch[cur][tr[t[(i - l) % m + 1] - 0]], b[i] = cur;
			T[1].update2(1, 1, n, l, min(l + 49, r));
			rep(i, min(l + 49, r) + 1, min(l + 49, r) + m) 
				cur = ch[cur][tr[t[(i - l) % m + 1] - 0]], a[i - min(l + 49, r)] = cur;
			Modify(1, min(l + 49, r) + 1, r, m, a);
			cur = T[1].query(1, 1, n, r, r);
			T[2].Get(1, 1, n, r + 1, min(r + 49, n));
			rep(i, r + 1, min(r + 49, n)) 
				cur = ch[cur][tr[c[i]]], b[i] = cur;
			T[1].update2(1, 1, n, r + 1, min(r + 49, n));
			// 修改 A 序列对应的 ACAM 上的节点序列 
			
			cur = T[1].query(1, 1, n, l - 1, l - 1);
			rep(i, l, min(l + 49, r)) 
				cur = ch[cur][tr[t[(i - l) % m + 1] - 0]], b[i] = g[cur][50];
			T[0].update2(1, 1, n, l, min(l + 49, r));
			rep(i, min(l + 49, r) + 1, min(l + 49, r) + m) 
				cur = ch[cur][tr[t[(i - l) % m + 1] - 0]], a[i - min(l + 49, r)] = g[cur][50];
			Modify(0, min(l + 49, r) + 1, r, m, a);
			T[1].Get(1, 1, n, r + 1, min(r + 49, n));
			rep(i, r + 1, min(r + 49, n)) b[i] = g[c[i]][50];
			T[0].update2(1, 1, n, r + 1, min(r + 49, n));
			// 修改 f 
		}
		else {
			ans = T[0].query(1, 1, n, l + 50, r);
			T[1].Get(1, 1, n, l, min(l + 49, r));
			rep(i, l, min(l + 49, r)) ans += g[c[i]][i - l + 1];
			printf("%lld\n", ans);
		}
	}
	return 0;
}
posted @ 2021-06-20 18:04  Achtoria  阅读(67)  评论(0编辑  收藏  举报