- 2022tysc1451 的博客
欧拉图
- 2025-8-5 9:28:31 @
欧拉图
欧拉图的定义
经过图中所有边,且每条边只经过一次的路径叫欧拉路径。
经过图中所有边,且每条边只经过一次的回路叫欧拉回路。
有欧拉回路的图叫欧拉图,有欧拉路径但没有回路的叫半欧拉图。
欧拉定理
如果一个图是欧拉图:
定理一:无向图为连通图且所有节点的度为偶数。
定理二:有向图的基图联通,且所有点的出度等于入度。
如果一个图是欧拉图:
定理一:无向图的起点和终点都是该图中唯一度数为奇数的点。
定理二:有向图的起点是该图仅有的一个出度比入度多一的点,终点是图仅有的一个入度比出度多一的点。
Hierholzer算法求欧拉路径
前提:已经知道该图为欧拉图(或半欧拉图)。
算法步骤:
1.确定起点:欧拉图可随意确定一个起点,有向半欧拉图需要一出度比入度多一的那个点作为起点,无向半欧拉图需要以任意一个奇数节点为起点。
2.删边:删除走过的边(点可以走多次但边不能)。
3.没有相连边的点入栈:一个节点变成独立的节点时,入栈。
4.最后输出栈内元素:这就是一个欧拉图的一种欧拉路径。
代码模板:
void dfs(int u){
for(int i=l;i<=r;i++){
if(g[u][i]){
g[u][i]--;
g[i][u]--;
dfs(i);
}
}
s.push(u);
}
#include<iostream>
#include<stack>
using namespace std;
int n,m,g[101][101],node[101],cnt,k;
stack<int> s;
void dfs(int u){
for(int i=1;i<=n;i++){
if(g[u][i]){
g[u][i]--;
g[i][u]--;
dfs(i);
}
}
s.push(u);
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
g[u][v]++,g[v][u]++;
node[u]++,node[v]++;
}
for(int i=1;i<=n;i++){
if(node[i]&1==1){
cnt++;
k=i;
}
}
if(cnt==2){
dfs(k);
while(!s.empty()){
cout<<s.top()<<" ";
s.pop();
}
}else if(cnt==0){
dfs(1);
while(!s.empty()){
cout<<s.top()<<" ";
s.pop();
}
}else cout<<"It isn't eulerian!";
}
例题:
A.欧拉回路
这道题貌似模板,只要知道欧拉图的性质就行了,既所有点的度数为偶数。于是就有了下面这一段代码:
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int n,m,a[1001];
int main(){
while(cin>>n){
if(n==0)return 0;
cin>>m;
memset(a,0,sizeof(a));
int cnt=0;
bool flag=true;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
a[u]++,a[v]++;
}
for(int i=1;i<=n;i++){
if(a[i]%2==1||a[i]==0){
flag=false;
break;
}
}
if(flag==false)cout<<"0\n";
else cout<<"1\n";
}
}
然后交上去就会发现90wa。
因为题目并没有说给出的图一定是连通图。
正确思路:
先从第一个节点开始深搜遍历,遍历到的点就标记起来,如果有没标记过的点,那么这个图就是非连通图,然后判断每个点的度数是否为偶数。
代码:
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int n,m,a[1001];
bool f[1001];
vector<int> g[1001];
void dfs(int u){
f[u]=true;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(!f[v])dfs(v);
}
}
int main(){
while(cin>>n){
if(n==0)return 0;
cin>>m;
memset(a,0,sizeof(a));
memset(f,false,sizeof(f));
for(int i=1;i<=1000;i++)g[i].clear();
int cnt=0;
bool flag=true;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
a[u]++,a[v]++;
}
dfs(1);
for(int i=1;i<=n;i++){
if(f[i]==false){
flag=false;
}
}
for(int i=1;i<=n;i++){
if(a[i]%2==1||a[i]==0){
flag=false;
break;
}
}
if(flag==false)cout<<"0\n";
else cout<<"1\n";
}
}
B.构建欧拉图
思路
计算每个点的度数,若一个点的度数为奇数,那么sum++
,最后输出sum/2
,(因为一条边可以连两个点)。
代码
#include<iostream>
using namespace std;
int n,m,a[1000001],sum;
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
a[u]++,a[v]++;
}
for(int i=1;i<=n;i++){
if(a[i]&1==1){
sum++;
}
}
cout<<sum/2;
}
C.骑马修删栏
模板的变形
思路
用Hierholzer算法,求欧拉路径,不过破损围栏的范围并不知道,需要用l和r求出最小的编号和最大的编号。
代码
#include<iostream>
#include<stack>
using namespace std;
int m,g[501][501],l=5000,r,node[501],cnt=1;
stack<int> s;
void dfs(int u){
for(int i=l;i<=r;i++){
if(g[u][i]){
g[u][i]--;
g[i][u]--;
dfs(i);
}
}
s.push(u);
}
int main(){
cin>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
l=min(min(l,u),v);
r=max(max(r,u),v);
g[u][v]++,g[v][u]++;
node[u]++,node[v]++;
}
for(int i=l;i<=r;i++){
if(node[i]&1==1){
cnt=i;
break;
}
}
dfs(cnt);
while(!s.empty()){
cout<<s.top()<<"\n";
s.pop();
}
return 0;
}
D.单词游戏
思路:
如果我们以盘子为节点建图,会发现这样程序很难跑。换个思路,我们可以以一个单词的首尾字母为节点,再进行建图,最后再dfs判断是否为欧拉图或半欧拉图。不过要注意的是,需要判断此图是否为连通图。另外,盘子是不能反转的,也就是说这个图本质上是一个有向图,不过为了方便判断是否为连通图,我们可以建一个无向图,再根据欧拉图的性质写出判断欧拉图的代码。
代码:
#include<iostream>
#include<cstring>
using namespace std;
int t,n,g[27][27],nodes[27],nodet[27],flag[27];
void dfs(int u){
flag[u]=2;
for(int i=1;i<=26;i++){
if(flag[i]==1&&g[u][i]){
flag[i]=2;
dfs(i);
}
}
}
int main(){
cin>>t;
while(t--){
memset(nodes,0,sizeof(nodes));
memset(nodet,0,sizeof(nodet));
memset(g,0,sizeof(g));
memset(flag,-1,sizeof(flag));
int k,a=0,b=0;
bool f=true;
cin>>n;
for(int i=1;i<=n;i++){
string z;
cin>>z;
int u=z[0]-'a'+1,v=z[z.size()-1]-'a'+1;
g[u][v]++;
g[v][u]++;
nodes[u]++;//出度
nodet[v]++;//入度
k=u;
flag[u]=flag[v]=1;
}
dfs(k);
for(int i=1;i<=26;i++){
int node=nodet[i]-nodes[i];
if(node==1)a++;
if(node==-1)b++;
if(a>1||b>1||flag[i]==1){
f=false;
break;
}
if(node!=0&&node!=1&&node!=-1){
f=false;
break;
}
}
if(!f)cout<<"The door cannot be opened.\n";
else cout<<"Ordering is possible.\n";
}
return 0;
}
E.划水
思路
建最短的边,使得原图变成欧拉图。算出每个节点需要的入度和出度,与周围最近的点进行匹配,最后输出建的新边长度之和。
代码
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,sum,s[100001],node[100001];
vector<int> in,out;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>node[i];
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
s[u]--,s[v]++;//出度--入度++
}
for(int i=1;i<=n;i++){
if(s[i]<0)for(int j=s[i];j<0;j++)in.push_back(node[i]);//需要入度
if(s[i]>0)for(int j=1;j<=s[i];j++)out.push_back(node[i]);
}
sort(in.begin(),in.end());
sort(out.begin(),out.end());
for(int i=0;i<in.size();i++){
sum+=abs(in[i]-out[i]);
}
cout<<sum;
}
F.原始生物
#include<iostream>
#include<vector>
using namespace std;
int n,m,in[1005],ou[1005],ans,l,r;
bool fla,vis[1005],exi[1005];
vector<int>g[1005];
void dfs(int u){
vis[u]=1;
if(in[u]!=ou[u])fla=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(!vis[v])dfs(v);
}
}
int main(){
cin>>m;
for(int i=1;i<=m;i++){
cin>>l>>r;
g[l].push_back(r);
g[r].push_back(l);
ou[l]++;in[r]++;
exi[l]=exi[r]=1;
n=max(n,max(l,r));
}
for(int i=1;i<=n;i++){
if(!exi[i])continue;
ans+=max(in[i],ou[i]);
if(!vis[i]){
fla=1;
dfs(i);
if(fla)ans++;
}
}
cout<<ans;
return 0;
}
G. Ant Trip
#include <iostream>
#include <vector>
#include <cmath>
#include <stack>
#include <cstring>
using namespace std;
int n,m,v[100001],f;
vector<int> a[100001];
void dfs(int x){
if(v[x])return;
v[x]=1;
for(int i=0;i<a[x].size();i++){
if(!v[a[x][i]]){
dfs(a[x][i]);
}
}
if(a[x].size()%2!=0)f++;
}
int main(){
while(cin>>n>>m){
for(int i=1;i<=10000;i++)a[i].clear();
memset(v,0,sizeof(v));
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
a[x].push_back(y);
a[y].push_back(x);
}
int s=0;
for(int i=1;i<=n;i++){
if(v[i]||a[i].size()==0)continue;
f=0;
dfs(i);
if(f==0)s++;
else s+=f/2;
}
cout<<s<<endl;
}
return 0;
}