从图中某个点出发遍历整个图,每条边通过且通过一次。
一、是否存在欧拉路或欧拉回路
(1)图应该是联通图:DFS或并查集
无向图:联通,用深搜或者并查集判断
有向图:弱联通:对于有向图G(V,E)的子集G`(V`,E`),称G`为若联通分量,若去掉边的方向后形成的无向图是联通图
(2)无向图(度数):全部都是偶点:存在欧拉回路
点度数为奇数的个数为0或2
(3)有向图(入度、出度):每个点的出度标记为1,入度标记为-1,出度+入度即为度数,
有向图存在欧拉路:只有1个度为1(起点),1个度为-1(终点),其他都为0
有向图存在欧拉回路:全部都为0
二、输出欧拉回路:
递归DFS,在后面打印或记录,但是如果数据很大,就得采用非递归形式。
三、混合图欧拉回路问题:最大流
欧拉回路和欧拉路径的几个概念:
欧拉环:图中经过每条边一次且仅一次的环;
欧拉路径:图中经过每条边一次且仅一次的路径;
欧拉图:有至少一个欧拉环的图;
半欧拉图:没有欧拉环,但有至少一条欧拉路径的图。
【无向图】
一个无向图是欧拉图当且仅当该图是连通的(注意,不考虑图中度为0的点,因为它们的存在对于图中是否存在欧拉环、欧拉路径没有影响)且所有点的度数都是偶数;一个无向图是半欧拉图当且仅当该图是连通的且有且只有2个点的度数是奇数(此时这两个点只能作为欧拉路径的起点和终点);
证明:因为任意一个点,欧拉环(或欧拉路径)从它这里进去多少次就要出来多少次,故(进去的次数+出来的次数)为偶数,又因为(进去的次数+出来的次数)=该点的度数(根据定义),所以该点的度数为偶数。
【有向图】
一个有向图是欧拉图当且仅当该图的基图(将所有有向边变为无向边后形成的无向图,这里同样不考虑度数为0的点)是连通的且所有点的入度等于出度;一个有向图是半欧拉图当且仅当该图的基图是连通的且有且只有一个点的入度比出度少1(作为欧拉路径的起点),有且只有一个点的入度比出度多1(作为终点),其余点的入度等于出度。
证明:与无向图证明类似,一个点进去多少次就要出来多少次。
https://blog.csdn.net/qq_35649707/article/details/75578102
Fleury算法:
1.判定该图是否为Euler图,包括有向欧拉通路,有向欧拉回路,无向欧拉通路,无向欧拉回路:
有向欧拉通路:起点:出度-入度=1,终点:入度-出度=1,其它点:入度==出度
有向欧拉回路:所有点:入度==出度
无向欧拉通路:仅有两个奇度点
无向欧拉回路:无奇度点
2.选择起点
3.采用dfs寻找Euler路径。
#include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <algorithm> using namespace std; /* 弗罗莱算法 */ int stk[1005]; int top; int N, M, ss, tt; int mp[1005][1005]; void dfs(int x) { stk[top++] = x; for (int i = 1; i <= N; ++i) { if (mp[x][i]) { mp[x][i] = mp[i][x] = 0; // 删除此边 dfs(i); break; } } } void fleury(int ss) { int brige; top = 0; stk[top++] = ss; // 将起点放入Euler路径中 while (top > 0) { brige = 1; for (int i = 1; i <= N; ++i) { // 试图搜索一条边不是割边(桥) if (mp[stk[top-1]][i]) { brige = 0; break; } } if (brige) { // 如果没有点可以扩展,输出并出栈 printf("%d ", stk[--top]); } else { // 否则继续搜索欧拉路径 dfs(stk[--top]); } } } int main() { int x, y, deg, num; while (scanf("%d %d", &N, &M) != EOF) { memset(mp, 0, sizeof (mp)); for (int i = 0; i < M; ++i) { scanf("%d %d", &x, &y); mp[x][y] = mp[y][x] = 1; } for (int i = 1; i <= N; ++i) { deg = num = 0; for (int j = 1; j <= N; ++j) { deg += mp[i][j]; } if (deg % 2 == 1) { ss = i, ++num; printf("%d ", i); } } if (num == 0 || num == 2) { fleury(ss); } else { puts("No Euler path"); } } return 0; }
一本通的题目:!!!!!!!!!!!看出来是欧拉图也很重要......
1527:【例 1】欧拉回路
有向图和无向图的欧拉回路,各自的判断方法
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> //上面的代码过不了所有点数据,不知道为什么 using namespace std; const int maxn=1e5+5; const int maxm=4e5+5; const int INF=0x3fffffff; typedef long long LL; int t,n,m; int in[maxn],out[maxn]; int vis[maxm]; int tot; int ans[maxm]; //记录路径的 int num,from[maxm],to[maxm],next[maxm]; void add(int x,int y){ to[++num]=y; next[num]=from[x]; from[x]=num; in[y]++; out[x]++; } void dfs(int x){ for(int &i=from[x];i;i=next[i]){ //加个引用,速度会快 int h=i; if(t==1){ //如果是无向图 int k=(i+1)>>1; //????这个是为什么 if(!vis[k]){ vis[k]=1; dfs(to[i]); //对目的地进行递归 if(h&1) ans[++tot]=k; else ans[++tot]=-k; } } else if(!vis[i]){ vis[i]=1; dfs(to[i]); ans[++tot]=h; } } } int main(){ scanf("%d",&t); int u,v; scanf("%d %d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d %d",&u,&v); add(u,v); if(t==1) add(v,u); } if(t==1){ for(int i=1;i<=n;i++){ if(in[i]&1) { cout<<"NO"<<endl; return 0; } } } if(t==2){ for(int i=1;i<=n;i++){ if(in[i]^out[i]){ cout<<"NO"<<endl; return 0; } } } dfs(to[1]); if(tot<m) { cout<<"NO"<<endl; return 0; } cout<<"YES"<<endl; for(int i=tot;i;i--) printf("%d ",ans[i]); return 0; } #pragma comment(linker, "/STACK:102400000,102400000") #include <bits/stdc++.h> using namespace std; const int N=100005,M=500005; inline int read() { int s=0,f=0; char ch=' '; while(!isdigit(ch)) { f|=(ch=='-'); ch=getchar(); } while(isdigit(ch)) { s=(s<<3)+(s<<1)+(ch^48); ch=getchar(); } return (f)?(-s):(s); } #define R(x) x=read() inline void write(int x) { if(x<0) { putchar('-'); x=-x; } if(x<10) { putchar(x+'0'); return; } write(x/10); putchar((x%10)+'0'); return; } inline void writeln(int x) { write(x); putchar(' '); return; } #define W(x) write(x),putchar(' ') #define Wl(x) writeln(x) int n,m,T; int Indeg[N],Outdeg[N],Deg[N]; namespace Oulahuilu { int tot=0,Next[M],to[M],head[N]; bool Arr[M]; inline void add(int x,int y) { Next[++tot]=head[x]; to[tot]=y; head[x]=tot; return; } int Huilu[M],Huilu_cnt=0; inline void Run(int x) { for(int &i=head[x];i;i=Next[i]) if(!Arr[i]) { int oo=i; Arr[i]=1; if(T==1) { (i&1)?(Arr[i+1]=1):(Arr[i-1]=1); } Run(to[i]); Huilu[++Huilu_cnt]=oo; } return; } inline void Output() { int i; for(i=Huilu_cnt;i>=1;i--) { if(T==1) { W(((Huilu[i]&1)?1:-1)*((Huilu[i]+1)/2)); } else { W(Huilu[i]); } } return; } } int main() { // freopen("tour14.in","r",stdin); // freopen("my.out","w",stdout); int i; R(T); R(n); R(m); if(T==1) { for(i=1;i<=m;i++) { int x,y; R(x); R(y); Deg[x]++; Deg[y]++; Oulahuilu::add(x,y); Oulahuilu::add(y,x); } for(i=1;i<=n;i++) if(Deg[i]&1) { return 0*puts("NO"); } for(i=1;i<=n;i++) if(Oulahuilu::head[i]) { Oulahuilu::Run(i); break; } if(Oulahuilu::Huilu_cnt!=m) { puts("NO"); } else { puts("YES"); Oulahuilu::Output(); } } else { for(i=1;i<=m;i++) { int x,y; R(x); R(y); Outdeg[x]++; Indeg[y]++; Oulahuilu::add(x,y); } for(i=1;i<=n;i++) if(Indeg[i]!=Outdeg[i]) { return 0*puts("NO"); } for(i=1;i<=n;i++) if(Oulahuilu::head[i]) { Oulahuilu::Run(i); break; } if(Oulahuilu::Huilu_cnt!=m) { puts("NO"); } else { puts("YES"); Oulahuilu::Output(); } } return 0; }
1528:【例 2】单词游戏
这个要注意,还有处理单词首尾的问题,就首尾是不同的单词,所以需要用并查集
有向图的欧拉路
//if(abs(in[i]-out[i])>1)
//if((in[i]||out[i])&&getfa(i)==i)
//if(ans1!=ans2||(ans1>1)||(ans2>1))
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+4; const int INF=0x3fffffff; typedef long long LL; int t,n; //没有考虑到首尾是有两个不同的,所以需要并查集,排除首位其实是一个单词的情况 //eg: //t...w; //b...b //b...b //b......b //a..a //这种情况,ans=3 //但是如果是下面这种: //a..w; //w...t; //t..a 这样的话最后并查集都是在一起的,所以ans=1,不会>1 int fa[30]; void prin(int x){ if(x==1) printf("The door cannot be opened. "); if(x==2) printf("Ordering is possible. "); } string str; int in[30]; int out[30]; int getfa(int x){ if(x==fa[x]) return x; else return fa[x]=getfa(fa[x]); } int main(){ scanf("%d",&t); while(t--){ int len; memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); for(int i=1;i<=26;i++) fa[i]=i; scanf("%d",&n); for(int i=0;i<n;i++) { cin>>str; len=str.length(); int x=str[0]-'a'+1,y=str[len-1]-'a'+1; in[y]++; out[x]++; fa[getfa(x)]=getfa(y); //同一个单词的首位放在一起 } int cnt=0; bool flag=0; for(int i=1;i<=26;i++){ if(abs(in[i]-out[i])>1) { flag=1; break; } } if(flag){ prin(1); continue; } for(int i=1;i<=26;i++){ if((in[i]||out[i])&&getfa(i)==i){ if((++cnt)>1){ //如果 break; } } } if(cnt>1){ prin(1); continue; } int ans1=0,ans2=0; for(int i=1;i<=26;i++){ if(in[i]>out[i]) ans1++; else if(out[i]>in[i]) ans2++; } if(ans1!=ans2||(ans1>1)||(ans2>1)) prin(1); else prin(2); } return 0; }
1530:Ant Trip
给你无向图的 N 个点和M 条边,保证这 M 条边都不同且不会存在同一点的自环边,现在问你至少要几笔才能所有边都画一遍。(一笔画的时候笔不离开纸)
//几个点,几个边,最少用几笔给他们连起来
统计一张不一定联通的无向图中欧拉路径数量
sol:用并查集维护联通性,一个联通块中的数量就是其中奇点个数/2,如果没有就是1, 集合个数=联通块
1)如果是单独的点,那就不要连接,就是0
2)如果是欧拉回路,奇点个数为0,就是1
3)剩下就是奇点个数/2
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=100010; const int INF=0x3fffffff; typedef long long LL; //几个点,几个边,最少用几笔给他们连起来 int fa[maxn]; int in[maxn]; int s[maxn]; //度为奇数的个数 int vis[maxn]; vector<int> t; int n,m; void inti(){ for(int i=1;i<=n;i++) fa[i]=i; } int finfa(int x){ if(x==fa[x]) return x; else return fa[x]=finfa(fa[x]); } int main(){ while(~scanf("%d %d",&n,&m)){ inti(); t.clear(); memset(in,0,sizeof(in)); memset(vis,0,sizeof(vis)); memset(s,0,sizeof(s)); int u,v; for(int i=0;i<m;i++){ scanf("%d %d",&u,&v); in[u]++; in[v]++; int fa1=finfa(u),fa2=finfa(v); fa[fa1]=fa2; } for(int i=1;i<=n;i++){ int x=finfa(i); if(!vis[x]){ //有多少集合:连通子图 vis[x]=1; t.push_back(x); } if(in[x]%2==1) s[x]++; //度为奇数的个数 } int ans=0; for(int i=0;i<t.size();i++){ int k=t[i]; cout<<k<<endl; if(in[k]==0) continue; //没有度,是孤立的 else if(s[k]==0) ans++; //是欧拉回路 else if(s[k]>0) ans+=s[k]/2; } printf("%d ",ans); } return 0; } #include <stdio.h> #include <vector> #include <string.h> using namespace std; #define N 100005 int n,m; int pre[N],du[N],vis[N],s[N]; vector<int>v; void intit() { for(int i=1;i<=n;i++) { pre[i]=i; } } int find(int x) { return x==pre[x]?x:pre[x]=find(pre[x]); } int main() { while(~scanf("%d %d",&n,&m)) { intit(); v.clear(); memset(vis,0,sizeof(vis)); memset(du,0,sizeof(du)); memset(s,0,sizeof(s)); for(int i=0;i<m;i++) { int a,b; scanf("%d %d",&a,&b); int x=find(a),y=find(b); du[a]++;du[b]++;//统计每个顶点的度 pre[x]=y; } for(int i=1;i<=n;i++) { int x=find(i); if(!vis[x]) { v.push_back(x);//把一个一个字图祖先 放在vector里边 vis[x]=1; } if(du[i]&1)//判断奇偶,筛选出来奇数度 s[x]++; } int ans=0; for(int i=0;i<v.size();i++) { int k=v.at(i); if(du[k]==0) continue;//k子集没有度 if(s[k]==0)//是欧拉回路 ans++; else ans+=s[k]/2; } printf("%d ",ans); } return 0; }
1531:John‘s Trip
无向图的欧拉回路
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; int mp[50][50]; //可以直接用邻接矩阵 int vis[50]; int st[2000]; int in[50]; int mnodes=0,mways=0,pos=0,top=0; void eulur(int cur,int mways){ for(int i=1;i<=mways;i++){ if(!vis[i]&&mp[cur][i]){ vis[i]=1; eulur(mp[cur][i],mways); st[++top]=i; //逆序存储 } } } int x,y,z; int main(){ while(scanf("%d %d",&x,&y)&&x&&y){ scanf("%d",&z); mnodes=0;mways=0;pos=0,top=0; memset(vis,0,sizeof(vis)); memset(in,0,sizeof(in)); memset(st,0,sizeof(st)); pos=min(x,y); mp[x][z]=y; mp[y][z]=x; in[x]++; in[y]++; mnodes=max(mnodes,max(x,y)); mways=max(mways,z); while(scanf("%d %d",&x,&y)&&x&&y){ scanf("%d",&z); mp[x][z]=y; mp[y][z]=x; in[x]++; in[y]++; mnodes=max(mnodes,max(x,y)); mways=max(mways,z); } bool flag=0; for(int i=1;i<=mnodes;i++){ if(in[i]&1) { flag=1;break; } } if(flag) printf("Round trip does not exist. "); else { eulur(pos,mways); cout<<st[top]; for(int i=top-1;i>0;i--) cout<<" "<<st[i]; cout<<endl; } } return 0; }
1532:太鼓达人
题目不太看得懂....
这道题就是说,让你找一个尽可能长的01环,让这个环不管从哪里开始,连续K位都不一样,要求找一个字典序尽可能小的
//https://blog.csdn.net/clove_unique/article/details/70160122
//https://www.sogou.com/link?url=DSOYnZeCC_rR_TP93bdO6Gz2zGiy_r9L8lvVhRSd8035Gd1-sQxs2Th3ZfoXRJBg
//这道题实际上是将k-1位的二进制数看做点,k位的二进制数看成边,并且连接两个点的边就是将这两个点的权怼起来
//然后每个点的入度和出度相等并且全部是偶点,是一个标准的欧拉图,所以只需要在这个图中找字典序最小的欧拉回路就行了
//可以贪心地找字典序较小的边,然后实在不行了就回溯
//这时间复杂度接近O(n+m)
or
把每个k位二进制数看做一个点
这样每个点能由两个点过来能走向两个点
所以每个点出度=入度,是欧拉图,而且存在欧拉回路
第一个答案就是2^k
然后,爆搜欧拉回路的复杂度是O(n)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1<<12; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //题目是真的看不懂........... int n,k,t,ans[maxn]; //记录答案 int vis[maxn]; bool dfs(int x,int now,int q){ if(x==INF) return 1; if(vis[now]) return 0; int nex=now; if(nex>=t) nex-=t; //如果i能够通过丢掉最高位再在后面补一个 0/1 得到 j,就从i向 j连一条边权为 0/1 的边。 nex=nex<<1; vis[now]=1; ans[x]=q; int to=x+1; if(to>k) to=1; if(to==n) to=INF; if(dfs(to,nex|0,0)) return 1; if(dfs(to,nex|1,1)) return 1; vis[now]=0; return 0; } int main(){ cin>>n; k=(1<<n); t=1<<(n-1); dfs(n,0,0); cout<<k<<" "; for(int i=1;i<=k;i++) printf("%d",ans[i]); return 0; }
1533:相框
还是理解不是很透彻。。。。。
这题主要还是分类讨论欧拉回路
首先对于导线一端没有东西的新建一个节点 由于原图不一定连通所以需要用到并查集判断有多少个连通块 将一条导线连接的两个焊点连接
然后先对于只有一个连通块考虑
1.如果一个焊点是孤立点 它对于导线无影响跳过
2.如果一个焊点度数大于2 它必须被烧熔
3.对于每两对奇点 它们必须相连 这样才满足欧拉回路
对于一个连通块处理后考虑多个连通块,必须把他们组合在一起
1.同样忽略孤立点
2.如果原图是一个环
需要找到一个点将其烧熔,才能继续组合
但其中若有焊点度数大于2,那么它本身已经被烧熔了所以可以略去此步
最后每个连通块向外和另一连通块连接一根导线就组装好了
3.原图有链 这种情况下只要将一对奇点向外连就好了 当然对于程序来说就是无需考虑有链的连通块连接的答案,以为这之前单个处理已经统计过了
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=101005; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int fa[maxn],d[maxn],flag1[maxn],flag2[maxn]; int findfa(int x){ if(x==fa[x]) return x; else return fa[x]=findfa(fa[x]); } void uoni(int x,int y){ int xx=findfa(x); int yy=findfa(y); if(xx!=yy) fa[xx]=yy; } int n,m,tot,ans,res; int main(){ scanf("%d %d",&n,&m); int x,y; for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++){ scanf("%d %d",&x,&y); if(!x) { x=++n;fa[x]=x; } if(!y){ y=++n;fa[y]=y; } uoni(x,y); d[x]++;d[y]++; } for(int i=1;i<=n;i++) { if(!d[i]) continue; //不考虑孤立点 if(d[i]&1){ //度为奇数 flag1[findfa(i)]=1; tot++; } if(d[i]>2){ //度数大于2,需要熔断 flag2[findfa(i)]=1; ans++; } } for(int i=1;i<=n;i++){ if(fa[i]==i&&d[i]){ res++; } } //怎样处理环???还不是很清楚 if(res>0){ for(int i=1;i<=n;i++){ if(findfa(i)==i&&d[i]&&!flag1[i]){ //度不为奇数 ans++; //需要相连接 if(!flag2[i]) ans++; //度数不大于2的话,ans++ } } } ans+=tot/2; printf("%d ",ans); return 0; }
1534:原始生物
这道题还比较好理解。。。。
首先是判断联通,上面有两道都这样了
深搜或者是并查集
所以,我看了题解差不多两种方法:
题意: 建了边(dfs判断联不连通)
给定一个有向图,求最少添加多少条边使得该图成为一个欧拉路径。输出欧拉路径上的总点数(具体参考样例)。
题解:
首先考虑简单情况,即这个图的基图是联通的情况。(基图:把有向图的边当成无向边重连出来的图)
若该图是一个欧拉回路,此时总点数等于总边数+1。(从一个点开始绕一圈还要回到该点,起点多遍历一次)
否则除了起点和终点,所有点的入度都应该等于出度,那么对于一个点u,它在欧拉路径上出现的次数就应该等于max{in(u),out(u)}。
推广到该图的基图是若干个联通块的情况,只需要对每个联通块求和即可。(每个联通块内的点数对其他块没有影响)
第二种方法:(这种方法没有建边,并查集判断联不连通)
欧拉路
将遗传密码的数当作点,遗传密码当作有向边,本题就是求出一种排列,包含每一个边。本题只用求排列的长度。如果在一个连通块中,构成了欧拉回路,则可以从任意一点出发构造数列,
最后要回到起始点,数列长度为边数 +1。如果图不含欧拉回路,则统计入度与出度的差值的绝对值,表明需要添加的边(即在数列中相邻但不是遗传密码)的 2倍(出、入各算一次)。此时,
再减去一条边,使图含有欧拉路,则数列的长度为边数+1 ,其中有一部分是增加的边。如果把减掉的一条边仍然计算,则数列长度为边数。
在统计数列长度时,先统计全部点入度与出度的差值的绝对值,再除以 2,得到增加的边数。若不含欧拉回路,则数列长度为原始边数加上增加边数。
再统计含欧拉回路的连通块,每一个连通块加 1。最后加上总边数。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; #define maxn 1000005 #define maxm 10000010 const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int head[maxn],to[maxn],nex[maxm]; int isr[maxn],vis[maxn],has[maxn]; int in[maxn],out[maxn]; int m,tot,cnt,n,ans; void adde(int x,int y){ to[++cnt]=y; nex[cnt]=head[x]; head[x]=cnt; } void dfs(int x){ //dfs判断联不连通 vis[x]=1; if(in[x]!=out[x]) isr[tot]=0; //不是欧拉回路 for(int i=head[x];i;i=nex[i]){ int tt=to[i]; if(!vis[tt]) dfs(tt); } } int main(){ scanf("%d",&m); int l,r; for(int i=0;i<m;i++){ scanf("%d %d",&l,&r); n=max(n,max(l,r)); adde(l,r); adde(r,l); has[l]=has[r]=1; in[r]++;out[l]++; } for(int i=1;i<=n;i++){ if(!vis[i]&&has[i]){ isr[++tot]=1;dfs(i); } } for(int i=1;i<=n;i++){ if(has[i]){ ans+=max(in[i],out[i]); //所有点的入度都应该等于出度,那么对于一个点u,它在欧拉路径上出现的次数就应该等max(in[i],out[i]) } } for(int i=1;i<=tot;i++) ans+=isr[i]; printf("%d ",ans); return 0; }