重叠的子串
给定一个长度为 n ( 1 ≤ ∣ s ∣ ≤ 1 0 5 ) n(1 \le \mid s \mid \le 10 ^ 5) n(1≤∣s∣≤105)的只由小写字母构成的字符串 s s s,有 m , ( 1 ≤ m ≤ 1 0 6 ) m, (1 \le m \le 10 ^ 6) m,(1≤m≤106)个询问:
每次询问给定 l , r l, r l,r,问 s s s是否存在一个字串 t t t,满足 ∣ t ∣ < 2 ( r − l + 1 ) \mid t \mid < 2(r - l + 1) ∣t∣<2(r−l+1),且 s [ l , r ] s[l, r] s[l,r]在 t t t中出现最少两次。
让 s [ l , r ] s[l, r] s[l,r]在 t t t上匹配,假设匹配的结束位置为 p 1 , p 2 , … p_1, p_2, \dots p1,p2,…,如果 t t t是一个合法串,则一定存在两个不同的数 i , j i, j i,j,
且满足 p i ≠ p j A N D a b s ( p i − p j ) < r − l + 1 p_i \ne p_j \ AND\ abs(p_i - p_j) < r - l + 1 pi=pj AND abs(pi−pj)<r−l+1,从这一点出发,我们考虑后缀自动机。
首先我们构建 S A M SAM SAM,建出 p a r e n t parent parent树,我们不难找到 e n d p o s = r endpos = r endpos=r的节点,考虑从这个点向上跳,
当我们跳到最上面的满足 l e n ≥ r − l + 1 len \ge r - l + 1 len≥r−l+1的节点时,显然这个点所代表的 e n d p o s endpos endpos集合中的字串,一定都含有 s [ l , r ] s[l, r] s[l,r]这个后缀。
简单的理解一下也就是 s [ l , r ] s[l, r] s[l,r]可以在这些节点匹配上,所以我们只要判断这些节点是否存在两个 e n d p o s endpos endpos满足其差值 ≤ r − l + 1 \le r - l + 1 ≤r−l+1即可。
这里我用的是线段树合并,对线段树上的节点记录其区间最左边有值的树,区间最有边有值的树,
以及当前区间的答案,然后合并上去即可,我们只要记录下每个 e n d p o s endpos endpos集合里面的最小的答案即可,整体复杂度 T × O ( n log n + m ) T \times O(n \log n + m) T×O(nlogn+m)。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int num, last, endpos[N << 1];
int head[N << 1], nex[N << 1], to[N << 1], cnt;
int fa[N << 1][21], ans[N << 1], idx[N], n, m;
int root[N << 1], ls[N << 6], rs[N << 6], tot;
char str[N];
struct Res {
int l, r, minn;
}a[N << 6];
struct statu {
int len, link, next[26];
}s[N << 1];
void push_up(int rt) {
if (ls[rt] && rs[rt]) {
a[rt] = {a[ls[rt]].l, a[rs[rt]].r, min({a[ls[rt]].minn, a[rs[rt]].minn, a[rs[rt]].l - a[ls[rt]].r})};
}
else if (ls[rt]) {
a[rt] = a[ls[rt]];
}
else {
a[rt] = a[rs[rt]];
}
}
void update(int &rt, int l, int r, int x) {
if (!rt) {
rt = ++tot;
}
if (l == r) {
a[rt] = {x, x, 0x3f3f3f3f};
return ;
}
int mid = l + r >> 1;
if (x <= mid) {
update(ls[rt], l, mid, x);
}
else {
update(rs[rt], mid + 1, r, x);
}
push_up(rt);
}
int merge(int x, int y, int l, int r) {
if (!x || !y) {
return x | y;
}
if (l == r) {
return x;
}
int mid = l + r >> 1;
ls[x] = merge(ls[x], ls[y], l, mid);
rs[x] = merge(rs[x], rs[y], mid + 1, r);
push_up(x);
return x;
}
void add(int x, int y) {
to[cnt] = y;
nex[cnt] = head[x];
head[x] = cnt++;
}
void Init() {
for (int i = 0; i < num; i++) {
s[i].len = s[i].link = endpos[i] = head[i] = root[i] = 0;
for (int j = 0; j < 26; j++) {
s[i].next[j] = 0;
}
}
num = last = 0;
for (int i = 1; i <= tot; i++) {
ls[i] = rs[i] = 0;
}
tot = 0;
}
void init() {
s[0].len = 0, s[0].link = -1;
num++, last = 0, cnt = 1;
}
void extend(int c, int id) {
int cur = num++;
s[cur].len = s[last].len + 1, endpos[cur] = id, idx[id] = cur;
int p = last;
while (p != -1 && !s[p].next[c]) {
s[p].next[c] = cur;
p = s[p].link;
}
if (p == -1) {
s[cur].link = 0;
}
else {
int q = s[p].next[c];
if (s[p].len + 1 == s[q].len) {
s[cur].link = q;
}
else {
int clone = num++;
s[clone] = s[q];
s[clone].len = s[p].len + 1;
while (p != -1 && s[p].next[c] == q) {
s[p].next[c] = clone;
p = s[p].link;
}
s[q].link = s[cur].link = clone;
}
}
last = cur;
}
void dfs(int rt, int f) {
fa[rt][0] = f;
for (int i = 1; i <= 20; i++) {
fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
}
for (int i = head[rt]; i; i = nex[i]) {
if (to[i] == f) {
continue;
}
dfs(to[i], rt);
root[rt] = merge(root[rt], root[to[i]], 1, n);
}
if (endpos[rt]) {
update(root[rt], 1, n, endpos[rt]);
}
ans[rt] = a[root[rt]].minn;
}
void build() {
for (int i = 1; i < num; i++) {
add(s[i].link, i);
}
dfs(0, -1);
}
int get(int rt, int len) {
for (int i = 20; i >= 0; i--) {
if (fa[rt][i] != 0 && fa[rt][i] != -1 && s[fa[rt][i]].len >= len) {
rt = fa[rt][i];
}
}
return rt;
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
int T;
scanf("%d", &T);
while (T--) {
scanf("%d %d %s", &n, &m, str + 1);
init();
for (int i = 1; i <= n; i++) {
extend(str[i] - 'a', i);
}
build();
for (int i = 1, l, r; i <= m; i++) {
scanf("%d %d", &l, &r);
int rt = get(idx[r], r - l + 1);
if (ans[rt] < r - l + 1) {
puts("Yes");
}
else {
puts("No");
}
}
Init();
}
return 0;
}