Solution - Atcoder ABC280Ex Substring Sort

rizynvu發表於2024-07-29

對於這種子串問題,且有多個基礎串,一個比較直觀的想法就是先上個廣義 SAM。

考慮 SAM 與字典序如何聯絡上。
因為跳 \(\operatorname{fail}\) 相當於是刪除子串的一個字首,直接這樣子明顯是不行的,因為跳了 \(\operatorname{fail}\) 字典序沒有一個很直觀地表示。
但是反過來考慮反串,那麼在反串上跳 \(\operatorname{fail}\) 就相當於是刪除字尾了。
發現這似乎就能聯絡上了。

下面一部分指的字串就是反串中對應的字串。

建出來 \(\operatorname{fail}\) 樹,節點 \(u\) 的兒子 \(v\) 按照 \(s_{v, \operatorname{len}(u) + 1}\) 排序即可(\(s_v\)\(v\) 節點對應的字串),就相當於是確定好了 \(u\) 的兒子字典序大小的相對順序。
這裡的正確性是考慮不會存在 \(u\) 的兒子 \(a, b\) 滿足 \(s_{a, \operatorname{len}(u) + 1} = s_{b, \operatorname{len}(v) + 1}\)

那麼對於 \(\operatorname{fail}\) 樹跑出來的 \(\operatorname{dfs}\) 序就會是對應的字典序的順序了。

對於詢問就比較簡單了。

對於節點 \(u\),這裡對應的字串數量有 \(\operatorname{cnt}_u\times (\operatorname{len}(u) - \operatorname{len}(\operatorname{fail}_u))\)
然後按照 \(\operatorname{dfs}\) 序做字首和 \(\operatorname{sum}_i\)

那麼對於詢問 \(x\)
找到最靠前的 \(i\) 使得 \(x\le \operatorname{sum}_i\)
同時如果知道了 \(i\) 節點對應 SAM 的節點 \(p\),就能知道字串的長度為 \(\operatorname{len}(\operatorname{fail}_u) + 1 + \lfloor\frac{x - \operatorname{sum}_{i - 1} - 1}{\operatorname{cnt}_p}\rfloor\),對於在哪個子串和右端點只需要初始建 Trie 時預處理,同時 \(\operatorname{fail}\) 樹上 dfs 時對應處理好即可。

因為題目滿足 \(x_j\) 遞增,那麼對應的 \(i\) 也是遞增的,就不需要二分了。

時間複雜度 \(\mathcal{O}((\sum\limits_i|s_i|)|\Sigma|\log |\Sigma| + q)\)

#include<bits/stdc++.h>
using ll = long long;
#define fi first
#define se second
const int maxn = 2e5 + 10;
int tr[maxn][26], m = 1;
std::pair<int, int> idw[maxn]; int siz[maxn];
std::string s[maxn];
int id[maxn], H[maxn], ed[maxn], cnt[maxn], len[maxn], ch[maxn][26], fail[maxn], tot = 1;
std::vector<int> son[maxn];
inline int append(int p, int c, int cnt_, int H_, int ed_) {
   int now = ++tot;
   len[now] = len[p] + 1, cnt[now] = cnt_, H[now] = H_, ed[now] = ed_;
   while (p && ! ch[p][c]) ch[p][c] = now, p = fail[p];
   if (! p) fail[now] = 1;
   else {
      int q = ch[p][c];
      if (len[p] + 1 == len[q]) fail[now] = q;
      else {
         int nq = ++tot;
         memcpy(ch[nq], ch[q], sizeof(ch[nq])), fail[nq] = fail[q], len[nq] = len[p] + 1;
         fail[q] = fail[now] = nq;
         while (p && ch[p][c] == q) ch[p][c] = nq, p = fail[p];
      }
   }
   return now;
}
void dfs1(int u) {
   for (int v : son[u])
      dfs1(v), cnt[u] += cnt[v], H[u] = H[v], ed[u] = ed[v];
}
ll sum[maxn]; int K, ids[maxn];
void dfs2(int u) {
   ids[++K] = u;
   sum[K] = sum[K - 1] + 1ll * cnt[u] * (len[u] - len[fail[u]]);
   for (int v : son[u])
      dfs2(v);
}
int main() {
   std::ios::sync_with_stdio(false);
   std::cin.tie(0), std::cout.tie(0);
   int n; std::cin >> n;
   for (int i = 1; i <= n; i++) {
      std::cin >> s[i];
      std::reverse(s[i].begin(), s[i].end());
      for (int u = 1, j = 0; j < s[i].size(); j++) {
         int &v = tr[u][s[i][j] - 'a'];
         if (! v) v = ++m;
         u = v, idw[u] = {i, j}, siz[u]++;
      }
   }
   id[1] = 1;
   for (int i = 1; i <= m; i++)
      for (int c = 0; c < 26; c++)
         if (tr[i][c])
            id[tr[i][c]] = append(id[i], c, siz[tr[i][c]], idw[tr[i][c]].fi, idw[tr[i][c]].se);
   for (int i = 2; i <= tot; i++)
      son[fail[i]].push_back(i);
   dfs1(1);
   for (int i = 1; i <= tot; i++)
      std::sort(son[i].begin(), son[i].end(), [&](int x, int y) {
         return s[H[x]][ed[x] - len[i]] < s[H[y]][ed[y] - len[i]];
      });
   dfs2(1);
   int Q; std::cin >> Q;
   for (int w = 1; Q--; ) {
      ll x; std::cin >> x;
      while (sum[w] < x) w++;
      int p = ids[w], l = len[fail[p]] + 1 + (x - sum[w - 1] - 1) / cnt[p];
      std::cout << H[p] << ' ' << (s[H[p]].size() - ed[p]) << ' ' << (s[H[p]].size() - ed[p] + l - 1) << '\n';
   }
   return 0;
}

相關文章