欧拉回路
简介:
- 欧拉回路:每条边恰好只走一次,并能回到出发点的路径.
- 欧拉路径:经过每一条边一次,但是不要求回到出发点.
- 欧拉图:图当且仅存在欧拉回路.
- 半欧拉图:图当且仅存在欧拉路径.
常规操作:
- 关于欧拉图的问题,一般是判回路的存在性或生成一个(半)欧拉图的代价或方案.
- 判欧拉图,即判存在回路,这里通过判点的度数来实现.
- 生成欧拉图的代价,类似于一笔画,这时候就要通过一些结论再判定一下即可.
- 求欧拉图的方案,一般即为无向图变有向图,同时满足欧拉图.
- 一般的,通过并查集来合并欧拉回路点集
欧拉回路的判定:
- 在无向图中,每个点的度数都是偶数,则存在欧拉回路。
- 在有向图中,每个点的入度等于出度,则存在欧拉回路。
欧拉路径的判定:
- 在无向图中,当且仅当所有点的度数为偶数或者除了两个度数为奇数外其余的全是偶数。
- 在有向图中,当且仅当该图所有点的度数为00或者 一个点的度数为,另一个度数为−1−1,其他点的度数为00。
有关判定回路的例题:HDU 1116 Play on Words
Description:
判断 n个单词是 否可以相连成一条链,两的件个单词是 否可以相连成一条链,两的件个单词是 否可以相连成一条链,两的件前一个单 词的最后字母和词。
Solution:
- 通过每个单词的首尾为边,记录点的入度与出度,
- 通过并查集判点集个数,进而判定是否为欧拉图。
Code:
#include<bits/stdc++.h>
using namespace std;
#define REP(i,f,t)for(int i=(f),i##_end_=(t);i<=i##_end_;i++)
#define SREP(i,f,t)for(int i=(f),i##_end_=(t);i<i##_end_;i++)
#define DREP(i,f,t)for(int i=(f),i##_end_=(t);i>=i##_end_;i--)
#define db double
#define LL long long
#define INF 0x3f3f3f3f
#define inf 0x3f3f3f3f3f3f3f
#define Sz(a)sizeof(a)
#define mcl(a,b)memset(a,b,Sz(a))
#define mcp(a,b)memcpy(a,b,Sz(b))
#define pb push_back
#define fi first
#define se second
template<class T>inline bool chkmin(T&x,T y){return y<x?x=y,1:0;}
template<class T>inline bool chkmax(T&x,T y){return x<y?x=y,1:0;}
inline LL Max(LL x,LL y){return x>y?x:y;}
inline LL Min(LL x,LL y){return x<y?x:y;}
typedef pair<int,int>PII;
#define N 30
int n;
char s[1002];
int In[N],Out[N],fa[N];
int find(int x){return x==fa[x]?x:find(fa[x]);}
void Clear(){
mcl(In,0);
mcl(Out,0);
SREP(i,1,N)fa[i]=i;
}
int main(){
int cas;scanf("%d",&cas);
while(cas--){
Clear();
scanf("%d",&n);
REP(i,1,n){
scanf("%s",s);
int len=strlen(s);
int x=s[0]-'a'+1;
int y=s[len-1]-'a'+1;
Out[x]++;
In[y]++;
x=find(x),y=find(y);
if(x!=y)fa[y]=x;
}
bool f=1;
int innum=0,outnum=0,root=0;
SREP(i,1,N){
if(!In[i] && !Out[i])continue;
if(fa[i]==i){
root++;
if(root>1){f=0;break;}
}
if(In[i]!=Out[i]){
if(In[i]-Out[i]==1)innum++;
else if(Out[i]-In[i]==1)outnum++;
else {f=0;break;}
}
}
if(f && ((!innum && !outnum) || (innum==1 && outnum==1)))puts("Ordering is possible.");
else puts("The door cannot be opened.");
}
return 0;
}
有关(半)欧拉图的代价问题: HDU 3018 Ant Trip
Description:
给出一张无向图,求最少几笔能画完这张图。
首先,我们需要知道一些性质:
- 孤立点,代价为画。
- (半)欧拉图,代价为11画。
- 非(半)欧拉图,代为为奇数度的点数/2画。
Prove:
- 首先,(半)欧拉图中点的度数都位偶数,那么就会有出度等于入度,故为1画;
- 其次,非(半)欧拉图中存在点的度数为奇数,
- 对于每1画,我们都会使一个连通量中的个奇数度点为偶数,
- 当最后一笔的时候我们必然消除所有的点的度数(包括剩下的两个奇点,因为最后一笔就必然是欧拉路径)。
Code:
#include<bits/stdc++.h> using namespace std; #define REP(i,f,t)for(int i=(f),i##_end_=(t);i<=i##_end_;i++) #define SREP(i,f,t)for(int i=(f),i##_end_=(t);i<i##_end_;i++) #define DREP(i,f,t)for(int i=(f),i##_end_=(t);i>=i##_end_;i--) #define db double #define LL long long #define INF 0x3f3f3f3f #define inf 0x3f3f3f3f3f3f3f #define Sz(a)sizeof(a) #define mcl(a,b)memset(a,b,Sz(a)) #define mcp(a,b)memcpy(a,b,Sz(b)) #define pb push_back #define fi first #define se second template<class T>inline bool chkmin(T&x,T y){return y<x?x=y,1:0;} template<class T>inline bool chkmax(T&x,T y){return x<y?x=y,1:0;} inline LL Max(LL x,LL y){return x>y?x:y;} inline LL Min(LL x,LL y){return x<y?x:y;} typedef pair<int,int>PII; #define N 100002 /* 此题我们可以知道一些性质: 1.孤立点 -> 0 画 2.(半)欧拉图 -> 1 画 3.非(半)欧拉图 -> 奇数度的点个数/2 画 */ int n,m; int fa[N]; int degree[N]; int sum[N],cnt[N]; // sum[i]表示该联通量的点数 // cnt[i]表示该联通量的奇数度点数 int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);} void Clear(){ mcl(degree,0); mcl(sum,0); mcl(cnt,0); REP(i,1,n)fa[i]=i; } int main(){ while(~scanf("%d%d",&n,&m)){ Clear(); REP(i,1,m){ int x,y; scanf("%d%d",&x,&y); degree[x]++,degree[y]++; x=find(x),y=find(y); if(x!=y)fa[x]=y; } REP(i,1,n){ int root=find(i); sum[root]++; cnt[root]+=(degree[i]&1); } int ans=0; REP(i,1,n){ if(sum[i]<2)continue; ans+=(!cnt[i])?1:(cnt[i]>>1); } printf("%d\n",ans); } return 0; }
有关生成欧拉图的方案问题:HDU 5348 MZL’s endless loop
Description:
给一个无向图,让你指定边的方向,比如 a→b 为 1,a←b 为 0,在给所有边指定方向后,对无向图上的每个点,如果满足|出度-入度|<2,那么输出任意一种方案。
Solution:
- 因为我们要满足条件,
- 那么就要尽可能有多的欧拉路径,
- 那么我们就根据入度与出度的大小关系,
- 进行dfs传递到该欧拉路径中。
Code:
#include<bits/stdc++.h> using namespace std; #define REP(i,f,t)for(int i=(f),i##_end_=(t);i<=i##_end_;i++) #define SREP(i,f,t)for(int i=(f),i##_end_=(t);i<i##_end_;i++) #define DREP(i,f,t)for(int i=(f),i##_end_=(t);i>=i##_end_;i--) #define db double #define LL long long #define INF 0x3f3f3f3f #define inf 0x3f3f3f3f3f3f3f #define Sz(a)sizeof(a) #define mcl(a,b)memset(a,b,Sz(a)) #define mcp(a,b)memcpy(a,b,Sz(b)) #define pb push_back #define fi first #define se second template<class T>inline bool chkmin(T&x,T y){return y<x?x=y,1:0;} template<class T>inline bool chkmax(T&x,T y){return x<y?x=y,1:0;} inline LL Max(LL x,LL y){return x>y?x:y;} inline LL Min(LL x,LL y){return x<y?x:y;} typedef pair<int,int>PII; #define N 100002 #define M 300002 int n,m; int qwq,head[N]; struct edge{ int to,next; }E[M<<1]; void addedge(int x,int y){E[qwq]=(edge){y,head[x]};head[x]=qwq++;} int degree[N],du[2][N]; bool vis[M<<1]; int ans[M]; void Clear(){ mcl(degree,0); mcl(du,0); mcl(vis,0); qwq=0; mcl(head,-1); mcl(ans,-1); } void dfs(int x,int op){ for(int &i=head[x];~i;i=E[i].next)if(!vis[i]){ int y=E[i].to; if(y!=x && du[op][y]<du[op^1][y])continue; vis[i]=vis[i^1]=1; if(i&1)ans[i>>1]=op^1; else ans[i>>1]=op; du[op][x]++; du[op^1][y]++; dfs(y,op); break; } } int main(){ int cas;cin>>cas; while(cas--){ scanf("%d%d",&n,&m); Clear(); SREP(i,0,m){ int x,y; scanf("%d%d",&x,&y); degree[x]++,degree[y]++; addedge(x,y),addedge(y,x); } REP(i,1,n) while(du[0][i]+du[1][i]<degree[i]) dfs(i,(du[0][i]<=du[1][i])?0:1); SREP(i,0,m)printf("%d\n",ans[i]); } return 0; }
Summary:
- 虽说欧拉图是一个比较冷门的考点,但是难起来还是非常玄学的…
蒟蒻太菜了 - 但是为了防冷门,这里还是初学了一些皮毛
除了判定,最多再求个方案或算个代价真的没了呀...