欧拉路径

欧拉路径

欧拉路径

定义:可以一笔画走完且不重复经过一条边的路径

可用欧拉路径走完有向连通图判定:

  1. 所有点入度与出度之差 \(\le1\)
  2. 入度与出度之差为1的点个数为0或2(总度数为偶数,这种点不可能只有1个,若有2个则一起点一终点)

可用欧拉路径走完无向连通图判定:

度数为奇数的点的个数为0或2(不可能只1个,理由同上,若有2个则一起点一终点)


插播道题—— 【一本通提高篇欧拉回路】单词游戏

题意:若一个单词的第一个字母与另一个单词最后一个字母相同,则这两个词可接龙,每个词只能用一次,问是否能把给出的所有词接龙成功。

这道题不难,关键在于如何建图

接龙只用看首尾,中间没用

我们可以将每个词看作从首字母连向尾字母的一条有向边,看整个图是否有欧拉路径走完

判定如上,就不放代码了


那么问题来了,一个无向连通图中有几条极长欧拉路径呢?(互不共用边)

也就是这个图最少几笔能画完?

有下列情况:

  1. 有一条欧拉路径走完,判定见上
  2. 设此图有n个度数为奇数的点(\(n\) 为偶数且 \(n \not=\) \(0\)\(2\)),则有 \(\frac{n}{2}\) 条欧拉路径

若图不连通就划分为几个连通图


如何找一条欧拉路径?

我们发现,从路径起点开始遍历图的每条边,回溯后将当前点加入path数组中,最后倒序输出即可

代码如下:

void dfs(int x)
{
    for(int i = minn; i <= maxn; i++)
        if(g[x][i])
        {
            g[x][i]--, g[i][x]--; // 邻接矩阵存图,把走过边删掉
            dfs(i);
        }
    path[++cnt] = x;
}                 
for(int i = minn; i <= maxn; i++)
   if(du[i] % 2)   
   {
      dfs(i); 
      flag = 1;
      break; // 无欧拉回路
   }
if(!flag)   dfs(minn);
for(int i = cnt; i > 1; i--)   printf("%d\n", path[i]);

P7771 【模板】欧拉路径

#include<bits/stdc++.h>
#define reg register
using namespace std;

const int N = 400010;
int n, m, u, v, ru[N], st, ed, chu[N], lin[N], cnt, head[N];
vector<int> edge[N];
inline void dfs(int x)
{
//	cerr << x << " "; 
	for(reg int i = head[x]; i < (int)edge[x].size(); i = head[x])
	{ // 注意,i = head[x],否则会跑满 m^2,时间复杂度不对
		head[x] = i + 1, dfs(edge[x][i]);
	}
	lin[++cnt] = x;
}
int main()
{
	scanf("%d%d", &n, &m);
	for(reg int i = 1; i <= m; ++i)
	{
		scanf("%d%d", &u, &v);
		edge[u].push_back(v), ++ru[v], ++chu[u]; 
	}
	for(reg int i = 1; i <= n; ++i)
		if(chu[i] != ru[i])
		{
			if(st && ed)	{printf("No");	return 0;}
			if(!st)	st = i;
			else if(!ed)	ed = i;	
		}
	if(st && !ed)	{printf("No");	return 0;}
	if(!st)	st = 1;
	for(reg int i = 1; i <= n; ++i)	sort(edge[i].begin(), edge[i].end());
	dfs(st);
	for(reg int i = cnt; i > 0; --i)	printf("%d ", lin[i]);
	return 0;
}

欧拉回路

定义:一条起点终点相同的欧拉路径

可用欧拉路径走完有向连通图判定:

  1. 所有点入度与出度之差<=1
  2. 入度与出度之差为1的点个数为0

可用欧拉路径走完无向连通图判定:

度数为奇数的点的个数为0

求路径同欧拉路径,若有字典序要求则把点/边排序

看道题—— 【一本通提高篇欧拉回路】欧拉回路

题目链接

思路则是求欧拉回路,不过输出可能会麻烦一些,要用边的号码判断反向或正向

代码如下——

#include<bits/stdc++.h>
using namespace std;
 
int t, n, m, v, u, du[100010], ru[100010], book[400010], path[100010], cnt;
vector<pair<int, int> > edge[100010];
int cmp(const pair<int, int> &a, const pair<int, int> &b)
{
   return a.second < b.second;
}
void dfs(int x)
{
   sort(edge[x].begin(), edge[x].end(), cmp); // 按字典序输出
   for(int i = 0; i < edge[x].size(); i++)
   {
       if(!book[edge[x][i].second])
       {
           book[edge[x][i].second] = 1;
           if(t == 1) 
           {
               if(edge[x][i].second <= m)   book[edge[x][i].second + m] = 1;
               else    book[edge[x][i].second - m] = 1;
           } //双向边两条都得去掉
           dfs(edge[x][i].first);
           if(t == 1)  path[++cnt] = edge[x][i].second > m ? (edge[x][i].second - m) * -1 : edge[x][i].second; // 判断方向
           else    path[++cnt] = edge[x][i].second; 
       }
   }
}
int main()
{
   scanf("%d%d%d", &t, &n, &m);
   for(int i = 1; i <= m; i++)
   {
       scanf("%d%d", &v, &u);
       edge[v].push_back(make_pair(u, i));
       if(t == 1)  edge[u].push_back(make_pair(v, i + m)), du[u]++, du[v]++;
       else    ru[u]++, du[v]++;
   }
   if(t == 1)
   {
       for(int i = 1; i <= n; i++)
           if(du[i] % 2)
           {
               printf("NO");
               return 0;
           }
   }
   else if(t == 2)
   {
       for(int i = 1; i <= n; i++)
           if(ru[i] != du[i])
           {
               printf("NO");
               return 0;
           }
   }
   // 判断有无欧拉回路
   for(int i = 1; i <= n; i++)
       if(du[i])
       {
           dfs(i);
           break;
       } // 找字典序最小点开始
   if(cnt != m)
   {
       printf("NO");
       return 0;
   }
   else
   {
       printf("YES\n");
       for(int i = cnt; i > 0; i--)
       {
           printf("%d ", path[i]); 
       } // 反向输出
   }
   return 0;
}
posted @ 2024-02-14 22:02  KellyWLJ  阅读(35)  评论(0编辑  收藏  举报