1. 程式人生 > 其它 >【XR-4】文字編輯器

【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;
}