主要思路
真就二合一?
显然是从左往右选是最佳的,所以可以考虑以下两种暴力:
- 建一个 SAM + 线段树,然后找区间内的串,然后从左往右跳。大概跳 \(\Theta(\frac{n}{r – l + 1})\) 次。
- 考虑直接倍增来进行连接,这样的话我们每次预处理一个长度 \(len\),然后对于不同的、长度为 \(len\) 的子串处理一个 \(nxt_{i, j}\),表示以 \(i\) 作为左端点、往右 \(2^j\) 个字符的下一个哈希值与 \([i, i + len – 1]\) 的字符串相同的位置,顺便处理贡献。知道这个之后我们就可以在 \(len\) 不变的情况下做 \(\log_2 n\) 次结束这个问题。
发现题目确实为这两种暴力提供了便利:对于 \(r – l + 1 \geq 2000\) 而言,直接用第一种暴力;对于小的范围 \(r – l + 1 \leq 51\) 而言,那就可以倍增,因为 \(nxt\) 数组最多只用处理 51 次即可。
代码
// P4493.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 3e5 + 200;
typedef long long ll;
typedef unsigned long long ull;
int n, K, q, pos[MAX_N], roots[MAX_N], bpos[MAX_N], blen[MAX_N];
char str[MAX_N], B[MAX_N];
ll ansBox[MAX_N];
struct queryInfo
{
int l1, r1, l2, r2, id;
bool operator<(const queryInfo &rhs) const { return r2 - l2 < rhs.r2 - rhs.l2; }
} queries[MAX_N];
namespace SegmentTree
{
struct node
{
int val, lson, rson;
} nodes[MAX_N * 60];
int ptot;
#define mid ((l + r) >> 1)
int update(int qx, int l, int r, int p)
{
if (p == 0)
p = ++ptot;
if (l == r)
{
nodes[p].val = l;
return p;
}
if (qx <= mid)
nodes[p].lson = update(qx, l, mid, nodes[p].lson);
else
nodes[p].rson = update(qx, mid + 1, r, nodes[p].rson);
nodes[p].val = max(nodes[nodes[p].lson].val, nodes[nodes[p].rson].val);
return p;
}
int merge(int x, int y, int l, int r)
{
if (x == 0 || y == 0)
return x + y;
else if (l == r)
{
int p = ++ptot;
nodes[p].val = l;
return p;
}
int p = ++ptot;
nodes[p].lson = merge(nodes[x].lson, nodes[y].lson, l, mid);
nodes[p].rson = merge(nodes[x].rson, nodes[y].rson, mid + 1, r);
nodes[p].val = max(nodes[nodes[p].lson].val, nodes[nodes[p].rson].val);
return p;
}
int query(int qx, int l, int r, int p)
{
if (l == r)
return l;
if (nodes[nodes[p].lson].val >= qx)
return query(qx, l, mid, nodes[p].lson);
else
return query(qx, mid + 1, r, nodes[p].rson);
}
#undef mid
} // namespace SegmentTree
namespace SAM
{
struct node
{
int ch[26], link, dep;
} nodes[MAX_N];
int ptot = 1, last_ptr = 1, bucket[MAX_N], idx[MAX_N], up[20][MAX_N];
void insert(int c)
{
int pre = last_ptr, p = last_ptr = ++ptot;
nodes[p].dep = nodes[pre].dep + 1;
while (pre && nodes[pre].ch[c] == 0)
nodes[pre].ch[c] = p, pre = nodes[pre].link;
if (pre == 0)
nodes[p].link = 1;
else
{
int q = nodes[pre].ch[c];
if (nodes[q].dep == nodes[pre].dep + 1)
nodes[p].link = q;
else
{
int clone = ++ptot;
nodes[clone] = nodes[q], nodes[clone].dep = nodes[pre].dep + 1;
nodes[p].link = nodes[q].link = clone;
while (pre && nodes[pre].ch[c] == q)
nodes[pre].ch[c] = clone, pre = nodes[pre].link;
}
}
}
void radixSort()
{
for (int i = 1; i <= ptot; i++)
bucket[nodes[i].dep]++;
for (int i = 1; i <= ptot; i++)
bucket[i] += bucket[i - 1];
for (int i = 1; i <= ptot; i++)
idx[bucket[nodes[i].dep]--] = i;
for (int i = ptot; i >= 1; i--)
if (nodes[idx[i]].link != 0)
roots[nodes[idx[i]].link] = SegmentTree::merge(roots[nodes[idx[i]].link], roots[idx[i]], 1, n);
for (int i = 1; i <= ptot; i++)
up[0][i] = nodes[i].link;
for (int i = 1; i < 20; i++)
for (int j = 1; j <= ptot; j++)
up[i][j] = up[i - 1][up[i - 1][j]];
for (int i = 1, p = 1, len = 0; i <= n; i++)
{
if (nodes[p].ch[B[i] - 'a'] != 0)
p = nodes[p].ch[B[i] - 'a'], len++;
else
{
while (p && nodes[p].ch[B[i] - 'a'] == 0)
p = nodes[p].link;
if (p == 0)
p = 1, len = 0;
else
len = nodes[p].dep + 1, p = nodes[p].ch[B[i] - 'a'];
}
bpos[i] = p, blen[i] = len;
}
}
int jump(int pos, int len)
{
int p = bpos[pos];
if (blen[pos] < len)
return 0;
for (int i = 19; i >= 0; i--)
if (up[i][p] && nodes[up[i][p]].dep >= len)
p = up[i][p];
return p;
}
ll solve(queryInfo curt)
{
int len = curt.r2 - curt.l2 + 1;
curt.r1 = min(curt.r1, K + len - 1);
int p = jump(curt.r2, len);
if (p == 0)
return 0;
if (SegmentTree::nodes[roots[p]].val < curt.l1 + len - 1)
return 0;
int stpos = SegmentTree::query(curt.l1 + len - 1, 1, n, roots[p]), edpos = curt.r1;
ll ret = 0;
while (stpos <= edpos)
{
if (stpos - len + 1 >= K)
break;
ret += K - (stpos - len + 1);
if (SegmentTree::nodes[roots[p]].val < stpos + len)
break;
stpos = SegmentTree::query(stpos + len, 1, n, roots[p]);
}
return ret;
}
} // namespace SAM
namespace Subtask2
{
const int bitnum = 133;
int nxt[20][MAX_N];
ull hash_val[MAX_N], pow_[MAX_N];
ll prod[20][MAX_N];
void processHash()
{
for (int i = pow_[0] = 1; i <= n; i++)
hash_val[i] = hash_val[i - 1] * bitnum + str[i] - 'a', pow_[i] = pow_[i - 1] * bitnum;
}
void processNxt(int len)
{
memset(nxt, 0, sizeof(nxt));
unordered_map<ull, deque<int> /**/> pre;
for (int i = 1; i + len - 1 <= n; i++)
{
ull chash = hash_val[i + len - 1] - hash_val[i - 1] * pow_[len];
while (!pre[chash].empty() && pre[chash].front() <= i - len)
nxt[0][pre[chash].front()] = i, pre[chash].pop_front();
pre[chash].push_back(i), prod[0][i] = K - i;
}
for (int i = 1; i < 20; i++)
for (int j = 1; j <= n - len + 1; j++)
{
nxt[i][j] = nxt[i - 1][nxt[i - 1][j]];
prod[i][j] = prod[i - 1][j] + prod[i - 1][nxt[i - 1][j]];
}
}
ll solve(queryInfo curt)
{
int len = curt.r2 - curt.l2 + 1;
curt.r1 = min(curt.r1, K + len - 1);
int p = SAM::jump(curt.r2, len);
if (p == 0)
return 0;
if (SegmentTree::nodes[roots[p]].val < curt.l1 + len - 1)
return 0;
int stpos = SegmentTree::query(curt.l1 + len - 1, 1, n, roots[p]) - len + 1, edpos = curt.r1 - len + 1;
if (stpos > edpos)
return 0;
ll ret = 0;
for (int i = 19; i >= 0; i--)
if (nxt[i][stpos] && nxt[i][stpos] <= edpos)
ret += prod[i][stpos], stpos = nxt[i][stpos];
ret += prod[0][stpos];
return ret;
}
} // namespace Subtask2
int main()
{
// freopen("cover4.in", "r", stdin);
// freopen("test.out", "w", stdout);
scanf("%d%d%s%s", &n, &K, str + 1, B + 1);
for (int i = 1; i <= n; i++)
{
SAM::insert(str[i] - 'a'), pos[i] = SAM::last_ptr;
roots[SAM::last_ptr] = SegmentTree::update(i, 1, n, roots[SAM::last_ptr]);
}
Subtask2::processHash(), SAM::radixSort(), scanf("%d", &q);
for (int i = 1; i <= q; i++)
scanf("%d%d%d%d", &queries[i].l1, &queries[i].r1, &queries[i].l2, &queries[i].r2), queries[i].id = i;
sort(queries + 1, queries + 1 + q);
for (int ptr = 1; ptr <= q; ptr++)
{
queryInfo curt = queries[ptr];
if (curt.r2 - curt.l2 + 1 <= 51)
{
if (ptr == 1 || curt.r2 - curt.l2 != queries[ptr - 1].r2 - queries[ptr - 1].l2)
Subtask2::processNxt(curt.r2 - curt.l2 + 1);
ansBox[curt.id] = Subtask2::solve(curt);
}
else
ansBox[curt.id] = SAM::solve(curt);
}
for (int i = 1; i <= q; i++)
printf("%lld\n", ansBox[i]);
return 0;
}