【CCF-CSP】201512-4 送货 C++


一、题目

在这里插入图片描述
在这里插入图片描述

原题目链接

二、解题

1.题目

经过一点时间的理解,题目的意思就是一个一笔画问题,把它转化为一个数学问题,就是判断图是否存在欧拉回路(Euler circuit)或欧拉路径(Euler path)

  • 欧拉定理,即一个有界的连通图(注意这里假设图是连通的)存在欧拉回路当且仅当每个顶点的度数均为偶数;存在欧拉路径当且仅当除了两个度数为奇数的顶点外其它点的度数均为偶数

1.2 算法思路:

  1. 存储无向图

  2. 判断图的连通性,如果不连通返回-1。

  3. 判断图中点的度数是否为偶数或者是否恰好有两个度数为奇数的点。

  4. 找到一个欧拉回路的起点,用dfs遍历整个欧拉回路。

2.代码

dev c++ 5.11

#include<iostream>
#include<set>
using namespace std;
const int N=10010,M=100010;
int p[N];
set<int> g[N];
int find(int x){
	if(x!=p[x]){
		p[x]=find(p[x]);//压缩路径 
	}
	return p[x]; 
}
int ans[M],top;
void dfs(int u){
	while(g[u].size()){
		int t=*g[u].begin();
		g[u].erase(t),g[t].erase(u);
		dfs(t);
	}
	ans[++top]=u;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) p[i]=i;
	while(m--){
		int a,b;
		cin>>a>>b;
		g[a].insert(b),g[b].insert(a);
		p[find(a)]=find(b);
	}
	
	int s=0;
	for(int i=1;i<=n;i++){
		if(find(i)!=find(1)){
			puts("-1");
			return 0;
		}
		else if(g[i].size() & 1) s++;
	}
	
	if(s!=0 && s!=2 || s==2 && g[1].size()%2==0){
		puts("-1");
		return 0;
	}
	
	dfs(1);
	if(top<n){
		puts("-1");
		return 0;
	}
	for(int i=top;i;i--){
		cout<<ans[i]<<" ";
	}
	return 0;
}


3.提交结果

在这里插入图片描述

总结

1.解释

  1. 存储图
// 存储图
    while( m --){
        int a, b;
        cin >> a >> b;
        g[a].insert(b), g[b].insert(a);
        // 将两个点连通起来
        p[find(a)] = find(b);
    }

这段代码片段是一个用并查集实现的图的建立过程,其中的 g 存储了图中所有节点的邻接关系,find 函数用来查找并查集中的根节点,p 数组存储了每个节点的父亲节点。

将两个点连通起来,实际上是将它们之间的连边加入到 g 的数据结构中。例如,假设输入的两个整数为 1 和 2,那么这两个节点将相互连通。代码中的 g[a].insert(b) 和 g[b].insert(a) 表示将节点 a 和节点 b 相互连接,也就是说它们之间存在一条无向边。

同时,这段代码还使用了并查集来维护每个节点所在的连通块。通过 p[find(a)] = find(b) 将节点 a 所在的连通块的根节点的父节点指向节点 b 所在的连通块的根节点,从而实现了将这两个节点所在的连通块合并的操作。也就是说,经过这个操作后,节点 a 和节点 b 所在的连通块已经被合并成为了一个大的连通块。

  1. 怎么判断一个图是否有欧拉回路呢?
    如果一个图是连通的,并且所有的点的度数都为偶数,那么这个图就有欧拉回路。
    如果一个图是连通的,并且只有两个点的度数为奇数,那么这个图就有欧拉通路(即从一个点出发,另一个点结束,并不会回到起点)。
    如果一个图不连通,那么它一定不存在欧拉回路。
  2. 并查集查找函数
int find(int x){
    if(x != p[x]) {
        p[x] = find(p[x]); // 路径压缩
    }
    return p[x];
}

路径压缩的目的是将查找路径上的所有节点的父节点直接指向根节点,以减少后续的查找时间,这是并查集的经典优化方式之一。

为了更好的理解,可以考虑一个简单的例子:假设当前要查找节点5的根节点,而5的父节点为3,3的父节点为2,2的父节点为1,1为根节点。如果不进行路径压缩,查找过程为 5 -> 3 -> 2 -> 1,这时将路径上所有节点的父节点直接指向1。然后下次查找节点2时,由于其父节点直接指向了1,查找路径就变成了2 -> 1,这样可以大大减少后续查找的时间。

  1. 奇数判断
else if(g[i].size() & 1) s++;

这段代码用来判断无向图中每个点的度数是否为偶数。如果某个点的度数为奇数,则将计数器 s 加一。

在这里, & 是位运算符,表示按位与。 g[i].size() 返回第 i 个节点的度数(即与之相邻的边的条数),如果其为奇数,则二进制末位为 1,与 1 进行按位与操作结果为 1,因此条件成立,将计数器 s 加一。

2.不分变量的含义

在这个程序中,变量的含义如下:

  • void dfs(int u):这是一个深度优先搜索(DFS)的函数,用于遍历图中的节点,参数u是当前节点的标号
  • set<int> g[N]:这是一个数组,表示图的邻接表,g[i]表示与节点i相邻接的所有节点
  • int p[N]:这是一个数组,表示并查集的父节点数组,p[i]表示i节点所在的集合的代表元素
  • int ans[M]、int top:这两个变量一起记录DFS遍历图的顺序,ans[i]表示第i步遍历到的节点,top表示ans数组中已经有多少个元素
  • *g[u].begin():这是一个指针,指向g[u]集合中的第一个元素,即与节点u相邻的第一个节点的标号
  • g[u].erase(t)、g[t].erase(u):这两行代码表示删除邻接表中的边,即节点u和节点t之间的边,这是因为遍历过的边需要被删除,避免重复遍历同一边。注意,由于是无向图,所以需要分别从g[u]和g[t]中删除对方的标号。
  • p[x]=find(p[x]):这是并查集的路径压缩操作,表示将节点x的父节点p[x]更新为p[x]所在集合的代表元素。
  • if(find(i)!=find(1)):这个if语句用于判断是否所有节点都在同一个连通块中。如果节点i所在的集合代表元素不是1,则表示与1节点不连通。
  • if(g[i].size() & 1) s++:这个语句用于计算奇度数节点的个数s。当节点i的相邻节点个数为奇数时,节点i的度数就是奇数,它也就是图中奇度数节点之一。
  • s!=0 && s!=2 || s==2 && g[1].size() % 2==0:这个if语句用于判断图是否满足欧拉回路的条件。当奇度数节点的个数为0时,图有欧拉回路;当奇度数节点的个数为2时,图有欧拉通路(即两端点的度数为奇数,其余节点的度数均为偶数);否则图中不可能存在欧拉回路。
  • cout<<ans[i]<<" ":这个语句表示输出DFS遍历图的结果,即欧拉回路中每个节点的标号。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值