HDU 3018 Ant Trip

HDU 3018 Ant Trip

一、题目意思

有一个团队的人要去逛小镇,这个镇是无向图,然后规定每条路只能走一次,且两个小镇之间只有一条小路。(就避免了多条路径的问题。)然后这个图有可能有连通分量,如果图有孤立点,那么这个点就忽略掉,(这个挺有用)。问要分为几组人马才能够把这个城市的小镇全部逛完。

二、解题思路

给你无向图的N个点和M条边,保证这M条边都不同且不会存在同一点的自环边,现在问你至少要几笔才能所有边都画一遍.(一笔画的时候笔不离开纸)

分析:

可能有多个连通分量,但我们挨个分析每一个连通分量即可:

  • 如果该连通分量是一个孤立的点,显然只需要0笔.

  • 如果该连通分量是一个 欧拉图半欧拉图,只需要1笔.

欧拉图(存在欧拉回路),半欧拉图(存在欧拉路径)
欧拉图是指通过所有边且每边仅通过一次的通路,相应的回路称为欧拉回路。

  • 本题的难点:现在关键是连通分量并非一个(半)欧拉图时,需要几笔?

这里先给出结论

非(半)欧拉图需要的笔数==该图中奇数度的点数目 / 2

下面来证明该结论:
首先一个无向图的连通分量中的奇数度的点个数一定是偶数个(成对出现),因为无向图的总度数=偶数.

我们在这种连通分量中每次画一笔有两种选择:

1.a>b>c>d>g 一条起点与终点不同的路径(路中除首尾度减1外,每个点度减2)

2.a>b>c>d>a 一条起点与终点相同的回路(路中每个点度数减2)

也就是说想要把非(半)欧拉图分量中的奇数度的点的度数都变成偶数,我们至少需要画奇数度点个数/2 笔(因为我们发现每一笔最多可以消除两个奇度数结点).

那么对于这种图我们最多需要画的笔数 是不是也是 : 奇数度点个数/2 笔呢?

答案是肯定的,这里假设图中有4个奇数度的点,1,2,3,4,5,6.如下图所示:

我们先走路:1>b>c>d>e>f>a>b>2 这条路.然后6,53,4 分别属于两个连通分量了.可以看出只需要3笔,即6/2=3即可.

当然也可以这么画:1>b>2 然后6>f>5 ,接着就剩下了一个半欧拉图了,半欧拉图也是1笔画,正好3笔.

也就是说对于这种非(半)欧拉图的连通分量,我们每笔必然消除正好2个点的奇度(使其度变偶数),当最后一笔的时候我们必然消除所有的点的度数(包括剩下的两个奇点,因为最后一笔就必然是欧拉通路).但是有一点要注意,有可能过程中的某几笔会使得该连通分量变成多个连通分量,当然结论不变.

经过上面的分析,这题的结论出来了,对于每个以i为根的连通分量我们记录属于该连通分量的点数目num[i]和该连通分量中奇度点的个数odd[i].

  • 如果num[i]==01,需0笔.(注意num[i]==0表示i点不是根,num[i]==1表示i点是一个孤立的点.)

  • 如果num[i]>1odd[i]==01

  • 如果num[i]>1odd[i]>0odd[i]/2

三、代码

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;

int n, m;
int p[N];   // 并查集的父亲数组
int d[N];   // 度
int odd[N]; // 奇数度节点个数

// 并查集
int find(int a) {
    if (a != p[a]) p[a] = find(p[a]);
    return p[a];
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("HDU3018.in", "r", stdin);
#endif
    while (~scanf("%d%d", &n, &m)) { // 多组测试数据
        // 并查集初始化
        for (int i = 1; i <= n; i++) p[i] = i;
        // 初始化度
        memset(d, 0, sizeof d);
        // 连通分量中奇数点的个数
        memset(odd, 0, sizeof odd);
        // m条边
        while (m--) {
            int a, b;
            scanf("%d%d", &a, &b);
            d[a]++, d[b]++;       // 维护度
            p[find(a)] = find(b); // 维护并查集
        }
        // 枚举每个节点
        for (int i = 1; i <= n; i++)
            odd[find(i)] += d[i] % 2; // 族长x身上记录了家族中所有节点奇数度的总个数

        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (find(i) == i) {          // 只统计族长
                if (d[i] == 0) continue; // 孤立的点,放过
                if (odd[i] == 0)         // 奇数度个数为0,则一笔画
                    cnt++;
                else
                    cnt += odd[i] / 2; // 否则是奇数点个数/2
            }
        }
        // 输出总的笔画数
        printf("%d\n", cnt);
    }
    return 0;
}

posted @   糖豆爸爸  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2021-08-07 P1621 集合题解
2021-08-07 P1525 [NOIP2010 提高组] 关押罪犯
2021-08-07 P1918 保龄球 题解
2021-08-07 P5266 【深基17.例6】学籍管理
2021-08-07 P5250 【深基17.例5】木材仓库 题解
2021-08-07 P3405 [USACO16DEC]Cities and States S 题解
2015-08-07 通过测试想到的一些问题
Live2D
点击右上角即可分享
微信分享提示