Loading

:D 获取中...

Solution - 造船

Link

gm 讲了一个根和非根的什么判断什么的,这里有一种(不一定)更加好理解的算法。

在并查集中,我们维护两个额外的信息,一个是 \(size_i\)(并查集内元素个数),一个是 \(d_i\)(并查集内可以操作多少次,即需要增加多少完成度)。每次输入 \(a_i\)\(b_i\),我们就将其合并到同一并查集中。

假设我们要合并 \(x\)\(y\),且 \(a = find(x)\)\(b = find(y)\),假如 \(a = b\)(在同一并查集内),我们就将 \(d_b\)(或者 \(d_a\),反正都一样)加一,代表我们又可以多增加一个完成度;假如 \(a \not= b\)(不在同一并查集内),我们就首先令 \(f_a = b\)(合并),然后把 \(d_a\)\(size_a\) 转移到 \(b\) 中,\(d_b\) 就需要加上 \(d_a + 1\)(这里的 \(1\) 是因为除了合并 \(a\)\(b\),还有操作本身加了一个完成度),\(size_b\) 则直接加上 \(size_a\) 即可。

所以我们修改一下合并并查集的代码:

void merge(int x, int y) {
	int a = find(x), b = find(y);
	if (a == b) d[b]++;
	else fa[a] = b, d[b] += d[a] + 1, siz[b] += siz[a];
}

所有的操作合并完之后,我们循环 \(i = 1 \sim n\),假如 \(find(i) = i\),我们就找到了一个并查集的「头头」,可以代表这个并查集直接进行操作。

因为初始时并查集中元素都为 \(0\),要想变成奇数的尽可能多,我们可以先让每个元素加一,因此我们比较 \(d_i\)\(size_i\)

假如 \(d_i > size_i\),那么 \(size_i\) 个元素全部变成 \(1\) 后还有剩余(为什么可以全部变成 \(1\),请自行思考),假如剩余次数(\(d_i - size_i\))是偶数,那没事,直接偶数偶数的加给任意元素,奇偶性不改变;假如剩余的是奇数,那么偶数偶数加完后还剩了 \(1\),随便加一个元素,这个元素就变成了偶数,答案减少一。

假如 \(d_i \leq size_i\),直接让 \(d_i\) 个元素变成 \(1\),统计答案即可。代码如下:

namespace liuzimingc {
const int N = 2e5 + 5;

int n, m, a[N], b[N], fa[N], d[N], ans, siz[N];

int find(int x) {
	if (x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}

void merge(int x, int y) {
	int a = find(x), b = find(y);
	if (a == b) d[b]++;
	else fa[a] = b, d[b] += d[a] + 1, siz[b] += siz[a];
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) fa[i] = i, size[i] = 1;
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", a[i], b[i]);
		merge(a[i], b[i]);
	}
	for (int i = 1; i <= n; i++)
		if (find(i) == i) 
			if (d[i] > siz[i]) ans += siz[i], d[i] -= siz[i], ans -= d[i] & 1;
			else ans += d[i], d[i] = 0;
	printf("%d\n", ans);
	return 0;
}
} // namespace liuzimingc

其实是懒得做题于是写一篇题解。

posted @ 2022-07-21 14:44  liuzimingc  阅读(85)  评论(0编辑  收藏  举报