紫丁香题解

更好的阅读体验

紫丁香 题解

前言

来自一场 noip \text{noip} noip 提高模拟赛的题目。

题目描述

n n n m m m 边的 简单无向连通图,点编号为 0 ∼ n − 1 0\sim n-1 0n1,要求删掉若干条边,最大化奇数度点的个数。

求:能得到最大答案的构造,用 m m m 长的 01 01 01 串表示, 1 1 1 表示保留该边, 0 0 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);
}

遍历边

然后一条一条边遍历,如果非树边,直接记录一下该边不用删,然后从自己已知爬到祖先,让每个度 ++。所以之前为什么要按秩并查集建树呢?——就是因为这里 f a i fa_i fai 必须记录自己的父亲,不可以压缩路径记录祖先,时间复杂度会 T \text{T} 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 ) ∈ E (u,v)\in E (u,v)E,考虑要不要保留。如果要保留,那把之前不连通时,各自的祖先 ( r o o t u , r o o t v ) (root_u,root_v) (rootu,rootv) 拿出来,断边。

再看这俩祖先的度有没有成为奇数。

如果任意一个是奇数,那就可以保留 ( u , v ) (u,v) (u,v) 该边,并且做更新度状态的操作(同上)。否则没有必要保留,先前断掉的 ( r o o t u , r o o t v ) (root_u,root_v) (rootu,rootv) 也不需要再次建边。

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 → r o o t u u\to root_u urootu 的路径上所有点都要度 ++?一说,为了该边路径上每个点的奇偶性(好像不太对)。

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值