Solution - 造船
Link。
gm 讲了一个根和非根的什么判断什么的,这里有一种(不一定)更加好理解的算法。
在并查集中,我们维护两个额外的信息,一个是 (并查集内元素个数),一个是 (并查集内可以操作多少次,即需要增加多少完成度)。每次输入 和 ,我们就将其合并到同一并查集中。
假设我们要合并 和 ,且 ,,假如 (在同一并查集内),我们就将 (或者 ,反正都一样)加一,代表我们又可以多增加一个完成度;假如 (不在同一并查集内),我们就首先令 (合并),然后把 和 转移到 中, 就需要加上 (这里的 是因为除了合并 和 ,还有操作本身加了一个完成度), 则直接加上 即可。
所以我们修改一下合并并查集的代码:
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];
}
所有的操作合并完之后,我们循环 ,假如 ,我们就找到了一个并查集的「头头」,可以代表这个并查集直接进行操作。
因为初始时并查集中元素都为 ,要想变成奇数的尽可能多,我们可以先让每个元素加一,因此我们比较 和 。
假如 ,那么 个元素全部变成 后还有剩余(为什么可以全部变成 ,请自行思考),假如剩余次数()是偶数,那没事,直接偶数偶数的加给任意元素,奇偶性不改变;假如剩余的是奇数,那么偶数偶数加完后还剩了 ,随便加一个元素,这个元素就变成了偶数,答案减少一。
假如 ,直接让 个元素变成 ,统计答案即可。代码如下:
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 by liuzimingc
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人