zoukankan      html  css  js  c++  java
  • 【XR-4】文本编辑器

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

    - Subtask 3

    首先会进入一个误区:维护修改,通过循环串的性质在 ( t 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) 开始的串在 ( t KMP) 自动机上成周期性的遍历,故 (f) 也从此位置开始呈长度为 (|t|) 的周期性变化。

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

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

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

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

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

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

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

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

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

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

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

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

    一样需要注意的是,此时我们假定可以快速得到 (S_{1, L - 1})( t 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) 序列在 ( t KMP) 自动机上遍历得到的节点序列修改后与 (f)一模一样 的周期性。

    由此我们使用维护 (f) 的方法来维护 (A) 序列在 ( t 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)( t ACAM) 上直接遍历并继承 (fail) 树上的答案即可。

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

    一次查询的答案显然为:

    [sumlimits_{i = L} ^ {L + d - 1} g_{i - L + 1, i} + sumlimits_{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),然后直接暴力调用 ( t 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
    ", ans);
    		}
    	}
    	return 0;
    }
    
    GO!
  • 相关阅读:
    关于JAVA中RSA加签解签,私钥加密公钥解密和公钥加密私钥解密代码详解
    Vue使用总结
    使用ReflectionToStringBuilder实现toString方法
    vue 发送短信验证码倒计时
    个人信息打码
    Token注解防止表单的重复提交
    html选择图片后直接预览
    从分布式一致性谈到CAP理论、BASE理论
    node.js 下依赖Express 实现post 4种方式提交参数
    web前端学习笔记(CSS变化宽度布局)
  • 原文地址:https://www.cnblogs.com/Go7338395/p/14907468.html
Copyright © 2011-2022 走看看