结局是什么,我们自己决定!——明日战记

Solution - 造船

Link

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

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

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

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

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=1n,假如 find(i)=i,我们就找到了一个并查集的「头头」,可以代表这个并查集直接进行操作。

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

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

假如 disizei,直接让 di 个元素变成 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 @   liuzimingc  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示