紫丁香 题解

紫丁香 题解

前言

来自一场 \(\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\) 的路径上所有点都要度 ++?一说,为了该边路径上每个点的奇偶性(好像不太对)。

现在我还没找到最准确的答案,现在属于似懂非懂。如果你想到了,欢迎评论交流。

posted @ 2023-11-06 20:21  一棵油菜花  阅读(11)  评论(0编辑  收藏  举报