区间插入,维护本质相同集合对数 (离线)
有 \(n\) 个集合,\(m\) 次操作,第 \(i\) 次操作选择一个区间 \([l_i,r_i]\) , 在这些集合里插入 \(i\) ,每次操作后查询本质相同集合对数。
先用可持久化线段树来存每个集合。然后利用类似 SA 的方式对集合按字典序排序。那么在任意时刻本质相同的集合都是一些连续段。考虑从 \(m\) 到 \(1\) 撤销操作,两个集合在 \(i = lcp\) 时由不同变得相同,合并他们的连续段就行。这个 \(lcp\) 可以用可持久化线段树来求。
这个是对集合按字典序排序的代码,从深到浅排序,有点类似 SA . 注意这个排序只会排同一层的点, rk 也只是在同一层节点中的排名。
void sort(int n) {
for (int i = 1; i <= n; ++i) ++cnt[a[i].y];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i; --i) b[cnt[a[i].y]--] = a[i];
for (int i = 0; i <= n; ++i) cnt[i] = 0;
for (int i = 1; i <= n; ++i) ++cnt[b[i].x];
for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i; --i) a[cnt[b[i].x]--] = b[i];
for (int i = 0; i <= n; ++i) cnt[i] = 0;
}
void Sort() {
rk[1] = 1;
for (int i = 19; i; --i) {
int cnt = 0;
for (int j = i + 1; j <= tot; j += 19) {
a[++cnt] = (Node){rk[ls[j]], rk[rs[j]], j};
}
sort(cnt);
int tmp = 0;
for (int j = 1; j <= cnt; ++j) {
tmp += a[j].x != a[j - 1].x || a[j].y != a[j - 1].y;
rk[a[j].id] = tmp;
}
}
}
//这个 flip 就是用来在主席树中插入/删除某一个元素的。即在当前集合中插入或删除 $x$.
int flip(int pre, int x, int l = 1, int r = 1 << 19) {
if (l == r) return pre ? 0 : 1;
int u = ++tot;
int mid = (l + r) >> 1;
if (x <= mid) {
ls[u] = flip(ls[pre], x, l, mid);
rs[u] = rs[pre];
}
else {
ls[u] = ls[pre];
rs[u] = flip(rs[pre], x, mid + 1, r);
}
return u;
}
//求 lcp
int lcp(int x, int y, int l = 1, int r = 1 << 19) {
if (l == r) return l - 1;
int mid = (l + r) >> 1;
if (rk[ls[x]] == rk[ls[y]]) {
return lcp(rs[x], rs[y], mid + 1, r);
}
else return lcp(ls[x], ls[y], l, mid);
}
for (int i = 1; i <= m; ++i) {
e[ex[i]].emplace_back(i);
e[ey[i]].emplace_back(i);
}
//建主席树
for (int i = 1; i < n; ++i) {
rt[i] = rt[i - 1];
sa[i] = i;
for (auto it : e[i]) {
//在当前集合中插入或删除 it.
rt[i] = flip(rt[i], it);
}
}
//给集合按字典序排序
Sort();
std::sort(sa + 1, sa + n, [](int x, int y) {
return rk[rt[x]] < rk[rt[y]];
});
//按 rk 排序后,求相邻集合的 lcp
for (int i = 1; i < n - 1; ++i) {
era[std::min(lcp(rt[sa[i]], rt[sa[i + 1]]), m)].emplace_back(i);
}
for (int i = 1; i < n; ++i) l[i] = r[i] = i;
ll sum = 0;
for (int i = m; i; --i) {
for (auto x : era[i]) {
//合并两个本质相同集合段
int L = l[x], R = r[x + 1];
sum += C2(R - L + 1) - C2(x - L + 1) - C2(R - x);
r[L] = R, l[R] = L;
}
ans[i] += sum;
}
for (int i = 1; i <= m; ++i) {
printf("%lld\n", ans[i]);
}