對於這種子串問題,且有多個基礎串,一個比較直觀的想法就是先上個廣義 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;
}