首页 > 其他分享 >欧拉路径学习笔记

欧拉路径学习笔记

时间:2024-10-20 16:34:14浏览次数:6  
标签:连通 int 路径 笔记 edge dfs 欧拉

简介

定义:

  • 欧拉回路:通过图中每条边恰好一次的回路
  • 欧拉通路:通过图中每条边恰好一次的通路
  • 欧拉图:具有欧拉回路的图
  • 半欧拉图:具有欧拉通路但不具有欧拉回路的图

摘自: oi-wiki

定义说白了就是小学的一笔画问题,这里直接给出三道例题。P7771 【模板】欧拉路径CF508DCF36E

例题

P7771 【模板】欧拉路径

思路

模板题,没有思路。直接讲一下求欧拉路径的方法即可。

首先,对于一个图,它要存在欧拉路径的必要条件很简单。就是奇点的个数要为 0 或者 2。其他情况都是无解的。这两种情况对应的图的形态长这样捏:

当然这也不是唯一解。其中第一个图中没有奇点,而第二个图中奇点为 1 和 2。

这里给出求解欧拉路径的伪代码:

void dfs(int u){
    遍历u的所有出边
        如果该边还存在
        删掉这条边
        dfs(该边去往的点)
    u入栈
}

这里给出一点 luogu 的大部分题解都没有解释到的地方。为什么不能遍历到一个点就将该点入栈,非要循环结束后才可以?这里举出一个例子:如果有一个无向图长得像一个 8 字,就像下面这样:

当我们走到 5 的时候会发生一个事情。就是说这个时候我们既可以选择往 1 走也可以往 4 走。我们希望的是向 4 走。如果我们最后再入栈那么当它 dfs 到 1 的时候就只会推掉 5 和 1 之间的部分,再从 5 开始继续搜索。相当于我们最开始的 \([1,7,6,5]\) 加上 \([5,4,3,5]\) 以及 \([5,1]\) 就是我们最后的答案。这里需要倒序输出,这里借鉴了 Marsrayd 大佬的 博客。这里是这样说的:

感性理解倒序输出的原因:如果是欧拉回路,那么遍历到哪,输出到哪也是对的,因为不管怎么走都会绕某个环走回起点,所以不到最后不会出栈,然而欧拉路径会出现边都被走过了,走不回起点,最后会停留在终点,遇到这种情况这种路径会最先出栈,于是只要把这个路径先走了,前面就和欧拉回路一样随便走就行,不会出栈,于是倒序输出就是对的

膜拜大佬。这个说得非常清楚啊。

CF508D

思路

此题很板啊。我们直接把这个字符串的前面的两个字符和后面的两个字符各看作一个点,连一条单向边。因为这题要求相同的字符串之间连线。那么我们把它转成 int 了之后就自然而然连上边了。

我们需要再记录一个每个点的出度和入度。来判断改图是否存在欧拉路径。

其实就是一个板题,这边给出可爱的代码:

AC code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
namespace WYL{
	const int N=3e5+10;
	const int INF=2e5;
	vector<int> edge[N];
	vector<int> point;
	int n,outde[N],inde[N],Start;
	string ans;
	void dfs(int u){
		while(!edge[u].empty()){
			int to=edge[u][edge[u].size()-1];
			edge[u].pop_back();
			dfs(to);
		}
		ans+=(char)(u%256);
	}
	int main(){
		cin>>n;
		memset(outde,0,sizeof(outde));
		memset(inde,0,sizeof(inde));
		string s;
		for(int i=1;i<=n;i++){
			cin>>s;
			int u=s[0]*256+s[1],v=s[1]*256+s[2];
			edge[u].push_back(v);
			outde[u]++;
			inde[v]++;
			Start=u;
		}
		int k=3e5;
		for(int i=0;i<=k;i++){
			if(edge[i].size()!=0){
				point.push_back(i);
			}
		}
		if(n==1){
			cout<<"YES"<<endl;
			cout<<s<<endl;
			return 0;
		}
		int flag=0,cnt=0;
		for(int i=0;i<=INF;i++){
			if(outde[i]!=inde[i]){
				flag=1;
			}
			if(abs(outde[i]-inde[i])==1){
				cnt++;
			}
			if(abs(outde[i]-inde[i])>1&&outde[i]!=inde[i]){
				cout<<"NO"<<endl;
				return 0;
			}
			if(outde[i]-inde[i]==1){
				Start=i;
			}
		}
		if(flag==1&&cnt!=2){
			cout<<"NO"<<endl;
			return 0;
		}
		dfs(Start);
		ans+=Start/256;
		if(ans.size()<n+2){
			cout<<"NO"<<endl;
			return 0;
		}
		cout<<"YES"<<endl;
		reverse(ans.begin(),ans.end());
		cout<<ans<<endl;
		return 0;
	}
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	WYL::main();
	return 0;
}

CF36E

写在前面的话

也许是本人太蒟了。本人认为本题的代码难度远高于 CF508D,但是 CF 的评分只差了 100 分(喜。

题意

翻译说得很清楚,这里再重复一下。

给你一张无向图,不保证连通以及可能有重边。让你求两条路径,使其正好能够覆盖所有的边,按边的编号输出答案。

思路

首先。这道题与 CF508D 的不同点在于这道题要求的是两条路径。于是我们尝试分类讨论。

因为不保证连通。我们首先知道,如果连通块的数量是 1 或者 2 的时候都有解。为什么?这个很显然,如果有大于两个的连通块。那么一定不存在两条路径能够覆盖掉所有的点。于是连通块数量大于 2 时我们可以直接把这个图 ban 掉。

好的现在我们来看连通块为 1 的情况。因为我们要求的路径相当于就是一个欧拉路径。所以在连通块为 1 的情况下度数为奇数的个数就只可能是 0,2 或者 4。其中呢 0 和 2 都比较好处理。只要当路径长度比 2 大就一定可以分成两坨。但是呢 4 就需要想一想了。这里提供一种蒟蒻的想法:我们找到两个没有连边的奇点将它们之间连上一条虚边(不是重链剖分里的那个捏)。跑一遍欧拉路径,然后再把那条虚边给删掉。得到的两个部分就是我们所求的,这里给出一个例子:

输入数据:

5
1 2 
1 4 
1 3
1 5
3 6

这里我连上了 5 与 6 之间的边。再删掉再删掉红色的边,就得到了点集为 \([1,4,5]\) 与 \([1,2,3,6]\) 的答案。

连通块为 1 的情况看完了。现在来研究连通块数量为 2 的。显然只有在两个连通块的奇点个数都为 0 或者都为 2 时才是有解的。如果有解直接各找一个欧拉路径就可以啦。

AC code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
namespace WYL{
	const int N=1e5+10;
	int n,to[N],nxt[N],head[N],tot,du[N],kid[N],kcnt,kminn[N];
	bool mark[N],vis[N];
	vector<int> jidian,path,jidian1,jidian2,path1,path2;
	void add(int u,int v){
		nxt[++tot]=head[u];
		head[u]=tot;
		to[tot]=v;
		return;
	}
	void dfs(int id,int k){
		kid[id]=k;
		for(int i=head[id];i;i=nxt[i]){
			if(kid[to[i]]==0&&du[to[i]]!=0){
				dfs(to[i],k);
			}
		}
		return;
	}
	int find_edge(int x,int y){
		for(int i=head[x];i;i=nxt[i]){
			if(to[i]==y&&!mark[(i+1)/2]){
				return i;
			}
		}
		return -1;
	}
	void Euler(int u,vector<int> &tmp){
		if(du[u]==0){
			tmp.push_back(u); 
			return;
		}
		for(int i=head[u];i;i=nxt[i]){
			// cout<<u<<"->"<<to[i]<<endl;
			if(vis[(i+1)/2]){
				continue;
			}
			vis[(i+1)/2]=true;
			du[to[i]]--;
			du[u]--;
			// cout<<"***"<<u<<"->"<<to[i]<<endl;
			Euler(to[i],tmp);
		}
		tmp.push_back(u);
		return;
	}
	void solve(int l,int r,vector<int> path){
		for(int i=l;i<=r;i++){
			int opt=(find_edge(path[i],path[i+1])+1)/2;
			cout<<opt<<" ";
			mark[opt]=true;
		}
		return;
	}
	int main(){
		cin>>n;
		if(n==1){
			cout<<"-1"<<endl;
			return 0;
		}
		for(int i=1;i<=n;i++){
			int x,y;
			cin>>x>>y;
			du[x]++;
			du[y]++;
			add(x,y);
			add(y,x);
		}
		for(int i=1;i<=10005;i++){
			if(du[i]&&kid[i]==0){
				kminn[kcnt+1]=i;
				dfs(i,++kcnt);
			}
			if(du[i]%2==1){
				jidian.push_back(i);
			}
		}
		if(kcnt>2){
			cout<<"-1"<<endl;
			return 0;
		}
		if(kcnt==1){
			if(jidian.size()==4){
				for(int i=0;i<4;i++){
					for(int j=i+1;j<4;j++){
						if(find_edge(jidian[i],jidian[j])!=-1){
							continue;
						}
						swap(jidian[0],jidian[i]);
						swap(jidian[1],jidian[j]);
						goto end;
					}
				}
				end:
				int x=jidian[0],y=jidian[1];
				du[x]++;
				du[y]++;
				add(x,y);
				add(y,x);
				memset(vis,0,sizeof(vis));
				Euler(jidian[2],path);
				int place=0;
				while((path[place]!=jidian[0]||path[place+1]!=jidian[1])&&(path[place]!=jidian[1]||path[place+1]!=jidian[0])){
					place++;
				}
				cout<<place<<endl;
				solve(0,place-1,path);
				cout<<endl;
				cout<<n-place<<endl;
				solve(place+1,path.size()-2,path);
				return 0;
			}else{
				path.clear();
				if(jidian.size()!=0&&jidian.size()!=2){
					cout<<"-1"<<endl;
					return 0;
				}
				if(jidian.size()==0){
					Euler(kminn[1],path);
				}else if(jidian.size()==2){
					Euler(jidian[0],path);
				}
				cout<<"1"<<endl;
				solve(0,0,path);
				cout<<endl;
				cout<<path.size()-2<<endl;
				solve(1,path.size()-2,path);
				return 0;
			}
		}else if(kcnt==2){
			for(int i=0;i<jidian.size();i++){
				if(kid[jidian[i]]==1){
					jidian1.push_back(jidian[i]);
				}else{
					jidian2.push_back(jidian[i]);
				}
			}
			if(jidian1.size()!=0&&jidian1.size()!=2){
				cout<<"-1"<<endl;
				return 0;
			}
			if(jidian2.size()!=0&&jidian2.size()!=2){
				cout<<"-1"<<endl;
				return 0;
			}
			path1.clear();path2.clear();
			if(jidian1.size()==0){
				int place=1;
				while(kid[place]!=1){
					place++;
				}
				Euler(place,path1);
			}else{
				Euler(jidian1[0],path1);
			}
			cout<<path1.size()-1<<endl;
			solve(0,path1.size()-2,path1);
			cout<<endl;
			if(jidian2.size()==0){
				int place=1;
				while(kid[place]!=2){
					place++;
				}
				Euler(place,path2);
			}else{
				Euler(jidian2[0],path2);
			}
			cout<<path2.size()-1<<endl;
			solve(0,path2.size()-2,path2);
			return 0;
		}
		return 0;
	}
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
	WYL::main();
	return 0;
}
/*
 10
 7 4
 7 4
 6 1
 2 3
 2 1
 1 6
 4 2
 8 4
 4 8
 3 5
*/

标签:连通,int,路径,笔记,edge,dfs,欧拉
From: https://www.cnblogs.com/OluMel/p/18487447

相关文章

  • 程序员都在用的笔记软件
    作为一个重度笔记控,最近入手了一款叫“闪思笔记”的软件,用了几天后,我决定来跟大家唠唠。这款软件真是妥妥的“笔记界全能选手”,下面简单给你们介绍下。首先,界面设计:它走的是极简风,打开的瞬间,感觉自己脑海中多了一片宁静的白板。没有那些杂七杂八的干扰元素,就像个井井有条的书......
  • STL-set学习笔记
    set本质是平衡数,插入的数会自动排序并去重插入s.insert(1)删除<1>erase(id)删除指针id指向的数<2>erase(lid,rid)删除lid到rid所指向区间的数,且该区间为前闭后开区间<3>erase(val)删除值val遍历set的遍历涉及指针,其数据类型为set<int>::iterator,因为是指针......
  • Living-Dream 系列笔记 第83期
    DSUontree又称tree上启发式合并。适用于统计子树内信息。原理:贪心。特征:通常需要一个全局的桶。实现方法:对于每个节点,先统计「轻子树」并清空桶,再统计「重子树」并保留桶。其中,「重子树」表示每个节点最大的子树,其余则称「轻子树」。通常需要离线询问。正确性说明:类似......
  • pa2学习笔记
    目录硬编码与软编码YEMUNEMU执行一条指令的过程ELF文件的组成ELF文件解析用fopen打开文件读取elfheader的信息解析elfheader解析sectionheaders解析符号表BIOS程序输入输出cpu与设备的交互方式(内存映射)(serial为例)(RTC为例)键盘的数据传输过程键盘的枚举宏定......
  • 系统架构设计师教程 第18章18.8 安全架构设计案例分析 笔记
    18.8安全架构设计案例分析18.8.1电子商务系统的安全性设计认证、授权和审计(AuthenticationAuthorizationandAccounting,AAA)是运行于宽带网络接入服务器上的客户端程序RADIUS软件主要应用于宽带业务运营的支撑管理,是一个需要可靠运行且高安全级别的软件支撑系......
  • 系统架构设计师教程 第18章 18.7 系统架构的脆弱性分析 笔记
    18.7系统架构的脆弱性分析18.7.1概述安全架构的设计核心是采用各种防御手段确保系统不被破坏,而系统的脆弱性分析是系统安全性的另一方面技术,即系统漏洞分析。漏洞的来源:1.软件设计时的瑕疵2.软件实现中的弱点3.软件本身的瑕疵4.系统和网络的错误配置18.7.2软件脆......
  • 沃顿商学院商业人工智能笔记-一-
    沃顿商学院商业人工智能笔记(一)P38:4_向上游移动客户体验.zh_en-GPT中英字幕课程资源-BV1Ju4y157dK在这个模块中,我们将讨论一些令人兴奋的内容。这是关于公司如何在客户旅程中向上游移动。现在我们谈到了预测客户旅程,使其更短。让我们尝试对比一下。首先以一个例子开始。......
  • 智源大会-2023-笔记-一-
    智源大会2023笔记(一)[2023北京智源大会]AI生命科学-P1-Mercurialzs-BV1KV4y117m5welcometothesymposiuaiforlifescience,i'msunny,i,thanktheorganersforgivingme。thehonortochthis,imposing,imposi,wehaveachangeintheprogram。unfortunatelyforper......
  • 智源大会-2023-笔记-五-
    智源大会2023笔记(五)尖峰对话&特邀报告(DavidHolz、张鹏、刘壮、ChristophSchuhmann)-P1-智源社区-BV14X4y1b7Jd所以你对,你好啊,欢迎大家加入我们今天下午对中程创始人的谈话,大卫·福尔摩斯先生我是大公园的张杰克,我很高兴能和你们一起,探索迷人的公司,并分享他们的创始人......
  • Maxwell学习笔记-入门了解
    目前,学习Maxwell已经两个月了,简单分享一下我的学习经验吧。(首次写博客,页面有些过于简洁,以后再学习怎么美化网页页面)1.软件安装首先是软件安装,Ansys的官网有免费的学生版,如果你还是在校生的话,千万不要错过这个机会。Ansys学生版|免费学生软件下载 在这个页面里往下滑,看重了......