紫丁香 题解
前言
来自一场 \(\text{noip}\) 提高模拟赛的题目。
题目描述
有 \(n\) 点 \(m\) 边的 简单无向连通图,点编号为 \(0\sim n-1\),要求删掉若干条边,最大化奇数度点的个数。
求:能得到最大答案的构造,用 \(m\) 长的 \(01\) 串表示,\(1\) 表示保留该边,\(0\) 表示不保留,输出字典序最大的方案。
思路
建树
首先,我们可以在图中建出一棵树。
由于最后要输出字典序最大,所以我们要尽可能地删后面的边。
所以可以用按秩(按秩序)并查集建出一棵树。注意,为什么要按秩?因为我们可以通过节点度数大小按秩建树,这样有利于树的深度不会太深。
code
int find(int x) {
// note:不能使用路径压缩
return fa[x] == x ? x : find(fa[x]);
}
bool merge(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry)
return 0;
if (d[rx] > d[ry])
swap(rx, ry);
Fa[++tot] = { rx, ry };
d[ry] += d[rx];
fa[rx] = ry;
return 1;
}
signed main() {
for (int i = m; i >= 1; i--)
chose[i] = merge(E[i].ft, E[i].sd);
}
遍历边
然后一条一条边遍历,如果非树边,直接记录一下该边不用删,然后从自己已知爬到祖先,让每个度 ++
。所以之前为什么要按秩并查集建树呢?——就是因为这里 \(fa_i\) 必须记录自己的父亲,不可以压缩路径记录祖先,时间复杂度会 \(\text{T}\),所以要是树的深度尽可能小。
code
void change(int x) {
while (x) {
d[x]++;
if (x == fa[x])
break;
x = fa[x];
}
}
signed main() {
for (int i = 1; i <= m; i++) {
if (chose[i] == 0) {
ans[i] = 1;
change(E[i].ft), change(E[i].sd);
}
}
}
这是为了将那些之前没有增加进树的度的点更新度的状态 \(^{[*]}\) (注释见结尾)。
如果遍历到了树边 \((u,v)\in E\),考虑要不要保留。如果要保留,那把之前不连通时,各自的祖先 \((root_u,root_v)\) 拿出来,断边。
再看这俩祖先的度有没有成为奇数。
如果任意一个是奇数,那就可以保留 \((u,v)\) 该边,并且做更新度状态的操作(同上)。否则没有必要保留,先前断掉的 \((root_u,root_v)\) 也不需要再次建边。
code
bool split(int x, int y) {
int rx = Fa[tot].ft, ry = Fa[tot].sd;
tot--;
d[ry] -= d[rx];
fa[rx] = rx;
if ((d[rx] & 1) || (d[ry] & 1)) {
change(x);
change(y);
return 1;
}
return 0;
}
signed main() {
for (int i = 1; i <= m; i++) {
if (chose[i])
ans[i] = split(E[i].ft, E[i].sd);
}
}
通过一系列操作,就可以得到哪些边要删,哪些边不用删了。
完整代码
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define ft first
#define sd second
const int MAXN = 6e5 + 5, MAXM = 9e5 + 5;
int n, m;
int tot;
int fa[MAXN], d[MAXN];
pair<int, int> E[MAXM], Fa[MAXM];
bool ans[MAXM], chose[MAXM];
int find(int x) {
// note:不能使用路径压缩
return fa[x] == x ? x : find(fa[x]);
}
bool merge(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry)
return 0;
if (d[rx] > d[ry])
swap(rx, ry);
Fa[++tot] = { rx, ry };
d[ry] += d[rx];
fa[rx] = ry;
return 1;
}
void change(int x) {
while (x) {
d[x]++;
if (x == fa[x])
break;
x = fa[x];
}
}
bool split(int x, int y) {
int rx = Fa[tot].ft, ry = Fa[tot].sd;
tot--;
d[ry] -= d[rx];
fa[rx] = rx;
if ((d[rx] & 1) || (d[ry] & 1)) {
change(x);
change(y);
return 1;
}
return 0;
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++) fa[i] = i, d[i] = 1;
for (int i = 1, u, v; i <= m; i++) {
scanf("%lld%lld", &u, &v);
u++, v++;
E[i] = { u, v };
}
for (int i = m; i >= 1; i--) chose[i] = merge(E[i].ft, E[i].sd);
for (int i = 1; i <= m; i++) {
if (chose[i])
ans[i] = split(E[i].ft, E[i].sd);
else {
ans[i] = 1;
change(E[i].ft), change(E[i].sd);
}
}
for (int i = 1; i <= m; i++) putchar(ans[i] ? '1' : '0');
return 0;
}
结尾
\(^{[*]}\):个人认为最难点在于 change
函数。为什么要从 \(u\to root_u\) 的路径上所有点都要度 ++
?一说,为了该边路径上每个点的奇偶性(好像不太对)。
现在我还没找到最准确的答案,现在属于似懂非懂。如果你想到了,欢迎评论交流。
标签:return,int,题解,rx,ry,tot,fa,紫丁香 From: https://www.cnblogs.com/wang-holmes/p/17813634.html