重叠的子串
给定一个长度为n(1≤∣s∣≤105)n(1 \le \mid s \mid \le 10 ^ 5)n(1≤∣s∣≤105)的只由小写字母构成的字符串sss,有m,(1≤m≤106)m, (1 \le m \le 10 ^ 6)m,(1≤m≤106)个询问:
每次询问给定l,rl, rl,r,问sss是否存在一个字串ttt,满足∣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]在ttt中出现最少两次。
让s[l,r]s[l, r]s[l,r]在ttt上匹配,假设匹配的结束位置为p1,p2,…p_1, p_2, \dotsp1,p2,…,如果ttt是一个合法串,则一定存在两个不同的数i,ji, ji,j,
且满足pi≠pjANDabs(pi−pj)<r−l+1p_i \ne p_j \ AND\ abs(p_i - p_j) < r - l + 1pi=pj AND abs(pi−pj)<r−l+1,从这一点出发,我们考虑后缀自动机。
首先我们构建SAMSAMSAM,建出parentparentparent树,我们不难找到endpos=rendpos = rendpos=r的节点,考虑从这个点向上跳,
当我们跳到最上面的满足len≥r−l+1len \ge r - l + 1len≥r−l+1的节点时,显然这个点所代表的endposendposendpos集合中的字串,一定都含有s[l,r]s[l, r]s[l,r]这个后缀。
简单的理解一下也就是s[l,r]s[l, r]s[l,r]可以在这些节点匹配上,所以我们只要判断这些节点是否存在两个endposendposendpos满足其差值≤r−l+1\le r - l + 1≤r−l+1即可。
这里我用的是线段树合并,对线段树上的节点记录其区间最左边有值的树,区间最有边有值的树,
以及当前区间的答案,然后合并上去即可,我们只要记录下每个endposendposendpos集合里面的最小的答案即可,整体复杂度T×O(nlogn+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;
}