##[$HDU$ $3018$ $Ant$ $Trip$](https://vjudge.net/problem/HDU-3018) ### 一、题目意思 有一个团队的人要去逛小镇,这个镇是无向图,然后规定每条路只能走一次,且两个小镇之间只有一条小路。(就避免了多条路径的问题。)然后这个图有可能有连通分量,如果图有孤立点,那么这个点就忽略掉,(这个挺有用)。问要分为几组人马才能够把这个城市的小镇全部逛完。 ### 二、解题思路 给你无向图的$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$.如下图所示: ![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/%7Byear%7D/%7Bmonth%7D/%7Bmd5%7D.%7BextName%7D/20230807115706.png) 我们先走路:$1-> b-> c-> d-> e-> f->a->b-> 2$ 这条路.然后$6,5$ 和$3,4$ 分别属于两个连通分量了.可以看出只需要$3$笔,即$6/2=3$即可. 当然也可以这么画:$1->b->2$ 然后$6->f->5$ ,接着就剩下了一个半欧拉图了,半欧拉图也是$1$笔画,正好$3$笔. 也就是说对于这种非(半)欧拉图的连通分量,我们每笔必然消除正好$2$个点的奇度(使其度变偶数),当最后一笔的时候我们必然消除所有的点的度数(包括剩下的两个奇点,因为最后一笔就必然是欧拉通路).但是有一点要注意,有可能过程中的某几笔会使得该连通分量变成多个连通分量,当然结论不变. 经过上面的分析,这题的结论出来了,对于每个以$i$为根的连通分量我们记录属于该连通分量的点数目$num[i]$和该连通分量中奇度点的个数$odd[i]$. - 如果$num[i]==0$或$1$,需$0$笔.(注意$num[i]==0$表示$i$点不是根,$num[i]==1$表示$i$点是一个孤立的点.) - 如果$num[i]>1$且$odd[i]==0$ 需$1$笔 - 如果$num[i]>1$且$odd[i]>0$ 需$odd[i]/2$笔 ### 三、代码 ```cpp {.line-numbers} #include 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; } ```