【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\)
由於將區間修改為週期變化的字串,那麼與從 \(L + d\) 開始與模式串的最長 \(border\) 每隔 \(|t|\) 個位置均相同。
則可知從 \(L + d\) 開始的串在 \(\tt KMP\) 自動機上成周期性的遍歷,故 \(f\) 也從此位置開始呈長度為 \(|t|\) 的週期性變化。
這意味著我們只需要在 \(\tt KMP\) 自動機上暴力遍歷 \(|t|\) 個節點即可求得 \([L + d, R]\) 這一段在修改後的 \(f\) 序列。
但需要注意的是,此時我們假定可以快速得到修改後的序列 \(S_{1, L + d - 1}\)
我們將求解這個節點的做法稱為「待解決的問題 \(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;
}