直接做是困难的,不妨依照部分分来思考。
- 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)) 的。
一次查询的答案显然为:
对于前半部分,我们直接暴力,单次复杂度 (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;
}