[NOI2018] 你的名字

[NOI2018] 你的名字

该来的总归还是会来的……

题意:

题目传送门

一句话题意:给出一个字符串\(T\)\(Q\)组询问,每次询问字符串\(S\)中有多少个本质不同的子串在\(T[l..r]\)中没有出现。(我们用\(T[l..r]\)表示截取\(T\)\([l, r]\)区间内的字符得到的字符串)

题解:

真的是对\(SAM\)的科技一无所知……

首先,做出这道题需要对\(SAM\)了解的足够深。我们先从部分分开始分析,假设\(l = 1, r = | T |\),即每次都查询的是整个\(T\),我们该怎么做呢?考虑每个\(S\)中的每一个右端点\(i\),都会存在一个\(l_i\)作为一个分界线,即\(\forall j < l_i,\)都有\(S[j..i]\)不是\(T\)的子串,\(\forall j \geq i,\)都有\(S[j..i]\)\(T\)的子串,这个还是比较明显的。并且容易发现的是,这个\(l_i\)是非递减的。于是我们就可以用\(two \ pointers\)来解决这个问题。

我们记\(L\)表示\(l_{i - 1}\)\(len\)表示当前匹配串长,\(o\)表示当前区间子串\(S[L..i - 1]\)\(T\)\(SAM\)中的位置,当\(i\)增大一时,我们尝试继续在\(SAM\)中转移,如果\(SAM\)中存在这条转移边,那么我们就把\(o\)移向这个转移节点,然后让匹配串长加一。否则我们不断的让匹配串长减一,即让\(L\)右移,然后继续判断\(SAM\)上是否存在转移边,直至\(len = 0\)或者存在转移边为止。注意当\(len = t[t[o].fa].len\)时,即当前串对应的节点已经是\(o\)的父亲节点时,需要移动\(o\)至父亲节点。这样我们就可以\(O(n)\)的求出每个点的\(l_i\)了。但是如果直接用\(l_i\)计算答案,会发现一些相同的子串会被重复计算贡献。

考虑如何去重,我们建出了\(SAM\)之后,尝试用\(SAM\)进行去重,我们记在\(SAM\)上的每个节点多记一个\(id\),表示其右端点在\(S\)中的位置,那么由于\(SAM\)中的节点所表示的子串都是本质不同的,那么我们枚举\(SAM\)上的节点计算答案是不会重复计算贡献的。记\(SAM\)的节点个数为\(tot\),答案就是\(\sum_{i = 1} ^ { tot } max(0, t[i].len - max(t[t[i].fa].len, l[t[i].id]))\)。这个还是比较好理解的,由于\(SAM\)中的一个节点里,所有串都是该节点长度最长的串的一个后缀,那么根据我们对于\(l_i\)的定义,该节点中长度小于等于\(l[t[i].id]\)的串都是\(T\)的子串,所以能够造成贡献的串就是长度在\(l[t[i].id] + 1\)\(t[i].len\)之间的串,注意和父亲节点的\(len\)取个\(\max\)。这样我们就可以得到\(68\)的好成绩。

接下来考虑如何处理\(l \neq 1, r \neq | T |\)的情况,分析一下,唯一不同的就是处理\(l_i\)的时候会出现问题。我们之前是如果存在转移边,那么就往转移边的方向走。但是实际情况是,这个转移边并不属于\([l..r]\)这个区间的,也就是说这个转移实际上是不合法的。所以我们需要处理这一类不合法的转移。实际上只需要维护\(SAM\)\(right\)集合即可,我们用线段树来记录一个节点的\(right\)集合。由于一个节点的\(right\)集合是其子节点的并,所以在建完\(SAM\)之后,用线段树合并将子节点的\(right\)集合合并至父亲节点的就行了。那么我们在存在转移边时,判断一下转移至的节点是的\(right\)集合是否有值在\([l + len, r]\)这个区间内即可,其余的照做就行了。

Code:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 50;
typedef long long ll;

int n, m, q;
char s[N], ss[N];

namespace Seg {
  int tot;
  int ls[N * 30], rs[N * 30];
  
  void Insert(int &o, int l, int r, int p) {
    if(!o) o = ++tot;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    if(mid >= p) Insert(ls[o], l, mid, p);
    else Insert(rs[o], mid + 1, r, p);
  }

  int Merge(int x, int y) {
    if(!x || !y) return x | y;
    int o = ++tot;
    ls[o] = Merge(ls[x], ls[y]);
    rs[o] = Merge(rs[x], rs[y]);
    return o;
  }

  int Query(int o, int l, int r, int L, int R) {
    if(!o) return 0;
    if(L <= l && r <= R) return 1;
    int mid = (l + r) >> 1;
    if(mid >= L && Query(ls[o], l, mid, L, R)) return 1;
    if(mid < R && Query(rs[o], mid + 1, r, L, R)) return 1;
    return 0;
  }
}

namespace SAM {

  struct node {
    int ch[27];
    int len, fa, rt, is;
  }t[N << 1];
  int tot = 1, las = 1;
  int id[N << 1], sa[N << 1];

  void Insert(int c) {
    int p = las, np = ++tot;
    t[np].len = t[p].len + 1; t[np].is = 1;
    while(p && !t[p].ch[c]) t[p].ch[c] = np, p = t[p].fa;
    if(!p) t[np].fa = 1;
    else {
      int q = t[p].ch[c];
      if(t[q].len == t[p].len + 1) t[np].fa = q;
      else {
        int nq = ++tot;
        t[nq] = t[q];
        t[nq].len = t[p].len + 1; t[nq].is = 0;
        t[q].fa = t[np].fa = nq;
        while(p && t[p].ch[c] == q) t[p].ch[c] = nq, p = t[p].fa;
      }
    }
    las = np;
  }

  void Build() {
    for(int i = 1; i <= n; i++) Insert(s[i] - 'a');
    for(int i = 1; i <= tot; i++) id[t[i].len] ++;
    for(int i = 1; i <= n; i++) id[i] += id[i - 1];
    for(int i = tot; i > 1; i--) sa[id[t[i].len] --] = i;
    for(int i = tot; i > 1; i--) {
      int p = sa[i];
      if(t[p].is) Seg::Insert(t[p].rt, 1, n, t[p].len);
      t[t[p].fa].rt = Seg::Merge(t[t[p].fa].rt, t[p].rt);
    }
  }
}

namespace Solver {
  
  struct node {
    int ch[27];
    int fa, len, id;
  }t[N << 1];
  int tot = 1, las = 1;
  int l[N << 1];

  void Clear() {
    for(int i = 0; i <= tot; i++) memset(t[i].ch, 0, sizeof t[i].ch), t[i].fa = t[i].len = t[i].id = 0;
    tot = las = 1;
  }
  
  void Insert(int c) {
    int p = las, np = ++tot;
    t[np].len = t[p].len + 1; t[np].id = t[np].len; 
    while(p && !t[p].ch[c]) t[p].ch[c] = np, p = t[p].fa;
    if(!p) t[np].fa = 1;
    else {
      int q = t[p].ch[c];
      if(t[q].len == t[p].len + 1) t[np].fa = q;
      else {
        int nq = ++tot;
        t[nq] = t[q];
        t[nq].len = t[p].len + 1;
        t[q].fa = t[np].fa = nq;
        while(p && t[p].ch[c] == q) t[p].ch[c] = nq, p = t[p].fa;
      }
    }
    las = np;
  }

  void main() {
    Clear();
    int L, R;
    scanf("%s", ss + 1);
    scanf("%d%d", &L, &R);
    m = strlen(ss + 1);
    int len = 0, o = 1;
    for(int i = 1; i <= m; i++) {
      int c = ss[i] - 'a';
      Insert(c);
      while(1) {
        if(SAM::t[o].ch[c] && Seg::Query(SAM::t[SAM::t[o].ch[c]].rt, 1, n, L + len, R)) {
          o = SAM::t[o].ch[c];
          len ++;
          break;
        }
        if(len == 0) break;
        len--;
        if(len == SAM::t[SAM::t[o].fa].len) o = SAM::t[o].fa; 
      }
      l[i] = len;
    }
    ll ans = 0;
    for(int i = 2; i <= tot; i++) {
      ans += max(0, t[i].len - max(t[t[i].fa].len, l[t[i].id]));
    }
    printf("%lld\n", ans);
  }
}

int main() {
  scanf("%s", s + 1);
  n = strlen(s + 1);
  SAM::Build();
  scanf("%d", &q);
  while(q--) Solver::main();
  return 0;
}

posted @ 2019-04-12 08:02  Apocrypha  阅读(627)  评论(0编辑  收藏  举报