P14164 [ICPC 2022 Nanjing R] 命题作文

给定一个包含 \(n\) 个点的链,\(m\) 次每次额外添加一条边,操作之间不独立。每次操作完询问有多少种方案选出两条边使得删除这两条边之后图不联通。

\[n,m \le 2.5\times10^5 \]


称额外添加的边为额外边,原来的 \(n-1\) 条边为原始边。

考虑什么样的边可以成为答案:

  • 其中一条是割边。

  • 其中一条原始边被且仅被另外一条额外边覆盖。

  • 两条不是割边的原始边,且覆盖他们的额外边构成的集合完全相同。

前两种情况是显然的。以下是第三种情况的证明:

证明

假设覆盖他们的额外边构成的集合完全相同,那么对于这两条边把原链切成的三段,所有的额外边要么连接这三段内部的点,要么连接最左边那一段和最右边那一段的点。那么中间那一段肯定和其他两段是分开的。

假设覆盖他们的额外边构成的集合不同,那么由于他们都不是割边,肯定存在一条边同时覆盖他们两个。又由于存在一条边覆盖其中一个但是不覆盖另外一个,那么肯定这三段都是联通的。

前两个本质是要维护被覆盖了 0/1 次的边的个数,这个是可以线段树简单维护的。

考虑如何维护第三种情况。正着做是比较困难的。我们考虑倒着做。

我们先找出最终状态下有哪些组,每条边在那个组里。然后找出每个时刻哪些组会被合并。

我们对每条边维护一个 01 串。表示这条边是否被第 \(i\) 条额外边覆盖。

那么我们对这些串按字典序排序,初始相邻的相同串在同一个组里,相邻的两个串的 LCP 就是他们被合并的时间。

那么这个过程用可持久化线段树维护哈希,是简单的。

\(\mathcal O(n\log^2 n)\)


#include <algorithm>
#include <iostream>
#include <vector>
#include <tuple>
#include <random>
#include <set>

const int N = 2.5e5 + 7;
#define rep(i,a,b) for(int i(a);i<=(b);++i)
typedef long long i64;

struct Tree1 {
	struct node {
		int l, r, cnt0, cnt1, tag;
	} tr[4*N];
#define ls (rt<<1)
#define rs (rt<<1|1)
#define self tr[rt]
	inline void addtag(int rt, int tag) {
		self.tag += tag;
		if(tag == 1) {
			self.cnt1 = self.cnt0, self.cnt0 = 0;
		} else {
			self.cnt0 = self.cnt1 = 0;
		}
	}
	inline void pushdown(int rt) {
		if(self.tag) {
			addtag(ls, self.tag);
			addtag(rs, self.tag);
			self.tag = 0;
		}
	}
	inline void pushup(int rt) {
		self.cnt0 = tr[ls].cnt0 + tr[rs].cnt0;
		self.cnt1 = tr[ls].cnt1 + tr[rs].cnt1;
	}
	void build(int l, int r, int rt=1) {
		self.l = l, self.r = r; 
		self.cnt0 = r - l + 1;
		self.cnt1 = self.tag = 0;
		if(l < r) {
			int m = (l + r) >> 1;
			build(l, m, ls), build(m+1, r, rs), pushup(rt);
		}
	}
	void modify(int x, int y, int rt=1) {
		if(x > self.r || self.l > y) return ;
		if(x <= self.l and self.r <= y) return addtag(rt, 1);
		return pushdown(rt), modify(x, y, ls), modify(x, y, rs), pushup(rt);
	}
} T;

std::mt19937_64 rnd(std::random_device{}());
typedef unsigned long long u64;
u64 hash[N];
struct hash_initualizer {
	hash_initualizer() {
		for(int i = 0; i < N; ++i) hash[i] = std::abs((i64)rnd());
	}
} _hash_initualizer;

int n, m;

#undef ls
#undef rs
struct Tree2 {
	struct node {
		int ls, rs;
		u64 hash;
	} tr[N*64];
	int idx = 1;
#define self tr[rt]
#define prev tr[pr]
	void update(int& rt, int pr, int x, int l=1, int r=m+1) {
		rt = ++idx;
		self = prev;
		self.hash ^= hash[x];
		if(l == r) { return ; }	
		int m = (l + r) >> 1;
		if(x <= m) update(self.ls, prev.ls, x, l, m);
		else update(self.rs, prev.rs, x, m+1, r);
	}
	std::pair<int, i64> query(int rt, int pr, int l=1, int r=m+1) {
		if(l == r) { return {l - 1, self.hash - prev.hash}; }
		int m = (l + r) >> 1;
		if(tr[self.ls].hash == tr[prev.ls].hash) return query(self.rs, prev.rs, m+1, r);
		else return query(self.ls, prev.ls, l, m);
	}
} S;

int c0[N], c1[N];

struct Query { int x, y; } q[N];
int rt[N];

int fa[N], siz[N];
inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

auto C = [](int x) { return 1ll * x * (x - 1) / 2; };

i64 sum, ans[N];
inline void merge(int x, int y) {
	x = find(x), y = find(y);
	if(x != y) {
		sum -= C(siz[x]), sum -= C(siz[y]);
		siz[y] += siz[x];
		fa[x] = y;
		sum += C(siz[y]);
	}
}

void clear() { S.idx = 1; }

std::basic_string<int> mod[N];
std::vector<std::pair<int, int>> con[N];
void solve() {
	std::cin >> n >> m;
	clear();

	T.build(1, n-1);
	rep(i, 1, n) mod[i].clear();
	rep(i, 1, m) con[i].clear();

	for(int i = 1; i <= m; ++i) {
		int l, r; std::cin >> l >> r;
		if(l > r) std::swap(l, r);
		if(l == r) {
			c0[i] = T.tr[1].cnt0;
			c1[i] = T.tr[1].cnt1;
			continue;
		}
		--r;
		mod[l] += i;
		mod[r+1] += i;
		T.modify(l, r);
		c0[i] = T.tr[1].cnt0;
		c1[i] = T.tr[1].cnt1;
	}

	for(int i = 1; i < n; ++i) {
		rt[i] = rt[i-1];
		for(int y: mod[i]) {
			S.update(rt[i], rt[i], y);
		}	
	}

	static int w[N];
	rep(i, 1, n) w[i] = i;
	std::set<std::pair<int, int>> set;
	std::stable_sort(w + 1, w + n, [&](int x, int y) -> bool {
		return S.query(rt[x], rt[y]).second < 0;
	});

	for(int i = 1; i <= n - 2; ++i) {
		int x = w[i], y = w[i+1];
		auto [p, diff] = S.query(rt[x], rt[y]);
		con[p].emplace_back(x, y);
	}

	sum = 0;
	rep(i, 1, n) fa[i] = i, siz[i] = 1;

	for(int i = m; i >= 1; --i) {
		for(auto&& [x, y]: con[i]) { merge(x, y); }
		i64 res = 0;
		res += 1ll * c0[i] * (n - 2 + i) - C(c0[i]);
		res += c1[i];
		res += sum - C(c0[i]);
		ans[i] = res;
	}
	
	for(int i = 1; i <= m; ++i) {
		std::cout << ans[i] << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
	int t; std::cin >> t; for(; t--; ) solve();
}
posted @ 2025-10-15 19:20  CuteNess  阅读(9)  评论(0)    收藏  举报