欧拉路径&&欧拉回路 //思路+例题(模板)
定义:
如果图G中的一个路径包括每个边恰好一次,则该路径称为欧拉路径(Euler path)。
如果一个回路是欧拉路径,则称为欧拉回路(Euler circuit)。
具有欧拉回路的图称为欧拉图(简称E图)。具有欧拉路径但不具有欧拉回路的图称为半欧拉图。
-------离散数学
判断
//对于图来说出度+入度是偶数,使用奇数度的顶点数肯定是偶数。
首先都要满足只有一个联通块。
无向图:
欧拉路径(满足一条):1.任意的v都是偶数的度 2.有两个v是奇数的度,其他都是偶数的度。
欧拉回路:任意的v都是偶数的度
有向图
欧拉路径(满足一条):1:任意的v出度==入度 2.有一个v出度=入度+1作为起点,有一个v入度==出度+1作为终点,其他出度==入度。
欧拉回路:任意的v出度==入度。
代码实现:
1.先判断是否构成欧拉图。
2.找欧拉路径时候遍历完出边再记录路径,最后逆序输出(算法的主要思想) 具体模拟:https://www.cnblogs.com/acxblog/p/7390301.html
3.勿忘判断是否只有一个联通块,可以使用并查集或直接判断遍历的点数是否达到要求。
4.multiset,删边时方便,logN。
Luogu P2731 骑马修栅栏 Riding the Fences //欧拉路径(无向图)
题意:求能否不重复的遍历所有无向边。
multiset保证字典序最小
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL mod = 100000007;
const int max_n = 505;
multiset<int>g[max_n];
multiset<int>::iterator it;
int path[5000];int p;
void dfs(int v){
while(!g[v].empty()){
it=g[v].begin();
int to=*it;
g[v].erase(it);g[to].erase(g[to].find(v));//遍历到就删除边
dfs(to);
}
path[++p]=v;//记录路径
}
int main(){
int m;scanf("%d",&m);for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);g[u].insert(v);g[v].insert(u);
}
int odd=0;int st=INT_MAX;
for(int i=1;i<=500;i++){
if(g[i].size()%2){odd++;st=min(i,st);}//奇数出边,记录起点。
}
if(odd==0) {//找最小的起点
for(int i=1;i<=500;i++){
if(g[i].size()){st=min(i,st);}
}
}
if(odd>2){
puts("-1");
return 0;
}
dfs(st);
for(int i=p;i>=1;i--)printf("%d\n",path[i]);//显然dfs记录的是逆序的
return 0;
}
UVa 10129 Play on Words // 欧拉路径(有向图)
题意:给定n个单词,判断能否将这些单词重排后链接起来,期中链接处单词ai的最后一个字母和a(i+1)的第一个字母要相同。
把每一个单词看成一条边,每一个字母当成一个顶点,判断是否是欧拉路径即可。这里可以不用dfs,维护并查集和出度入度即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL mod = 100000007;
const int max_n = 505;
int fa[50];int in[50],ou[50];
int found(int x){
if(fa[x]==x)return x;
return fa[x]=found(fa[x]);
}
int main(){
int T;scanf("%d",&T);while(T--){
for(int i=0;i<='z'-'a';i++){fa[i]=i,in[i]=ou[i]=0;}
int n;scanf("%d",&n);for(int i=1;i<=n;i++){
string s;cin>>s;int u=s[0]-'a';int v=s[s.length()-1]-'a';
ou[u]++;in[v]++;
int fx=found(u),fy=found(v);
if(fx!=fy)fa[fx]=fy;
}
int st=-1,ed=-1,book=0;
for(int i=0;i<='z'-'a';i++){
if(in[i]==ou[i])continue;
if(in[i]==ou[i]+1&&ed==-1){ed=i;continue;}//判断能否作为终点
if(in[i]==ou[i]-1&&st==-1){st=i;continue;}//判断能否作为起点
book=1;break;//度数出现问题
}
if(book){puts("The door cannot be opened.");continue;}
int cnt=0;
for(int i=0;i<='z'-'a';i++){
if((ou[i]||in[i])&&found(i)==i)cnt++;//找到一个联通块
}
if(cnt>1){puts("The door cannot be opened.");continue;}
puts("Ordering is possible.");
}
return 0;
}
CF DIV2 E. Neko and Flashback //欧拉路径(有向图)+ 离散化
题意:给一个序列ai,一个全排列pi,然后可以求出b'和c'。
现在已知 b'和c',问是否可以组成合法序列ai,如果能,输出任意一个。
这个题是真的有质量... 官方题解:https://codeforces.com/blog/entry/66696
发现b'i c'i只会和api ap(i+1)有关,b'i和c'i并且是肯定是取到其中一个的,表示相邻的关系可以所有的b'i c'i连一条无向边,最后求欧拉路径。这样的想法似乎是没有问题的()。
最后需要离散化一下。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL mod = 100000007;
const int max_n = 1e5+5;
int b[max_n],c[max_n];
vector<int>v;
multiset<int>g[max_n*2];
vector<int>path;
void dfs(int now){
while(!g[now].empty()){
set<int>::iterator it;
it=g[now].begin();
int to=*it;
g[now].erase(it);g[to].erase(g[to].find(now));
dfs(to);
}
path.push_back(now);
}
int main(){
int n;scanf("%d",&n);
for(int i=1;i<n;i++)scanf("%d",&b[i]),v.push_back(b[i]);
for(int i=1;i<n;i++)scanf("%d",&c[i]),v.push_back(c[i]);
sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
for(int i=1;i<n;i++){
if(b[i]>c[i]){printf("-1");return 0;}
int idu=lower_bound(v.begin(),v.end(),b[i])-v.begin();
int idv=lower_bound(v.begin(),v.end(),c[i])-v.begin();
g[idu].insert(idv); g[idv].insert(idu);
}
int odd=0;int st=0;
for(int i=0;i<(int)v.size();i++){
if(g[i].size()%2){odd++;st=i;}
}
if(odd>2){printf("-1");return 0;}
dfs(st);
if((int)path.size()!=n){printf("-1");return 0;}//多于一个联通块
for(int i=path.size()-1;i>=0;i--) printf("%d ",v[path[i]]);
return 0;
}