\(\text{LOJ \#6041. 「雅礼集训 2017 Day7」事情的相似度 题解}\)
解法一
由 parent 树的性质得到,前缀 \(s_i,s_j\) 的最长公共后缀实质上就是 \(i,j\) 在 SAM 中的 \(\operatorname{LCA}\) 在 SAM 中的 \(\operatorname{len}\)。让我们考虑如何处理 \((l,r)\) 区间内的询问。直接考虑求 \((l,r)\) 的贡献是困难的,不妨转变思路,求每个点对于子树内的贡献。对于一个点 \(x\),我们可以容易地用启发式合并来合并出子树内所有的节点。考虑加入子树 \(y\) 的贡献后怎么做:不难发现对于一个 \(a\),我们只需要统计离 \(a\) 最近的 \(p,q(p\le a,a\le q)\)。这样我们在合并的时候顺便用 std::set
可以求出。由启发式合并的复杂度可以知道,这样做的总复杂度是 \(O(n\log^2n)\)。 然后我们得到了 \(O(n\log n)\) 个贡献三元组 \((l,r,w)\) 和 \(m\) 个询问 \((l,r)\),实际上是一个二维偏序问题,排序后树状数组维护即可。总的时间复杂度是 \(O(n\log^2n)\) 的。
代码:
#include <bits/stdc++.h>
#define N 2000005
#define M 2
using namespace std;
int n, m;
char s[N];
int tot = 1, lst = 1;
struct SAM {int len, fa;int s[M];
} sam[N];
set<int>st[N];
void insert(int c, int x) {int p = lst, np = lst = ++tot;sam[np].len = sam[p].len + 1;for (; !sam[p].s[c]; p = sam[p].fa) sam[p].s[c] = np;if (!p) sam[np].fa = 1;else {int q = sam[p].s[c];if (sam[q].len == sam[p].len + 1) sam[np].fa = q;else {int nq = ++tot;sam[nq] = sam[q], sam[nq].len = sam[p].len + 1;sam[np].fa = sam[q].fa = nq;for (; sam[p].s[c] == q; p = sam[p].fa) sam[p].s[c] = nq; }}st[lst].insert(x);
}vector<int>v[N];
struct node {int r, vl;
};
vector<node>upd[N], que[N];void dfs(int x) {for (auto y : v[x]) {dfs(y);if (st[x].size() < st[y].size()) swap(st[x], st[y]);st[x].insert(-1e9), st[x].insert(1e9);for (auto i : st[y]) {auto it = --st[x].upper_bound(i);if ((*it) != -1e9) upd[*it].push_back({i, sam[x].len});it = st[x].lower_bound(i);if ((*it) != 1e9) upd[i].push_back({*it, sam[x].len});}st[x].erase(-1e9), st[x].erase(1e9);st[x].insert(st[y].begin(), st[y].end());st[y].clear();}
}int lbt(int x) {return x & -x;
}
int tr[N];
void add(int x, int vl) {while (x <= n) tr[x] = max(tr[x], vl), x += lbt(x);
}
int ask(int x) {int ans = 0;while (x) ans = max(tr[x], ans), x -= lbt(x);return ans;
}int ans[N];int main() {ios::sync_with_stdio(0);cin.tie(0);cin >> n >> m;cin >> s;for (int i = 0; i < n; i++) insert(s[i] - '0', i + 1);for (int i = 2; i <= tot; i++) v[sam[i].fa].push_back(i);dfs(1);for (int i = 1; i <= m; i++) {int l, r;cin >> l >> r;que[l].push_back({r, i});}for (int i = n; i >= 1; i--) {for (auto p : upd[i]) add(p.r, p.vl);for (auto p : que[i]) ans[p.vl] = ask(p.r);}for (int i = 1; i <= m; i++) cout << ans[i] << "\n";return 0;
}
解法二
实际上本题是信息搜集题,我将给出符合题目描述的图片,相信大家能推断出本题真正的答案。