2021CCPC华为云挑战赛:HDU 7091 重叠的子串(SAM + 线段树合并)
重疊的子串
給定一個長度為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(nlog?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; }總結
以上是生活随笔為你收集整理的2021CCPC华为云挑战赛:HDU 7091 重叠的子串(SAM + 线段树合并)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网络设备自动化运维工具——Nornir3
- 下一篇: Theano深度学习框架之Lasagne