2021CCPC华为云挑战赛:HDU 7091 重叠的子串(SAM + 线段树合并)

重叠的子串

给定一个长度为 n ( 1 ≤ ∣ s ∣ ≤ 1 0 5 ) n(1 \le \mid s \mid \le 10 ^ 5) n(1s105)的只由小写字母构成的字符串 s s s,有 m , ( 1 ≤ m ≤ 1 0 6 ) m, (1 \le m \le 10 ^ 6) m,(1m106)个询问:

每次询问给定 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(rl+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(pipj)<rl+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 lenrl+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 rl+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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值