首页 > 其他分享 >区间插入,维护本质相同集合对数 (离线)

区间插入,维护本质相同集合对数 (离线)

时间:2023-02-13 22:22:05浏览次数:51  
标签:lcp int 离线 插入 ls 集合 对数 排序

有 \(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]);
}

标签:lcp,int,离线,插入,ls,集合,对数,排序
From: https://www.cnblogs.com/i209M/p/17118052.html

相关文章