欧拉路径/回路 小专题
欧拉路径:从图上一个点 \(S\) 经过所有边恰好一次到 \(T\) ,而且 S T 不必相同
欧拉回路:S和T相同
复杂度: \(O(n+m)\)
UOJ117
有向图欧拉回路和无向图欧拉回路的差别主要在于判断无解的时候以及判重(因为一条无向边只能走一次,但是add了两次)
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long ll;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5 + 5;
// O(n+m)
int n,m;
vector<int>g[maxn];
struct edges{
int to,next;
}ed[maxn<<1];
int head[maxn],ecnt=0;
void add(int x,int y){
ed[++ecnt].to=y;
ed[ecnt].next=head[x];
head[x]=ecnt;
}
namespace undirected_euler{
int de[maxn];
int ans[maxn<<1],acnt=0;
int vis[maxn<<1]; // 注意 vis 记录的是边,双向边需开2倍
void euler_un(int x){ // 当前搜到 x 号点
for(;head[x]!=-1;){
int curedge=head[x]; // 一条边解决了之后就相当于删掉
head[x]=ed[curedge].next;
if(!vis[curedge]){
vis[curedge]=vis[curedge^1]=1; // 两条无向边拆成的边都不能走
euler_un(ed[curedge].to);
if(curedge&1)ans[++acnt]=-(curedge>>1); // 对应的是同一条无向边(因为无向,所以有2倍边)
else ans[++acnt]=((curedge>>1));
}
}
}
void undirect(){
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
ecnt=1; // 确保无向边的标号都是 2&3 4&5 .. 这样
int x=0,y=0;
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add(y,x);add(x,y); // 注意加边顺序!否则会引起正负方向相反
++de[x];++de[y];
}
for(int i=1;i<=n;i++) // 度数一定是偶数
if(de[i]%2!=0)return (void)puts("NO");
euler_un(x);
if(acnt!=m){ // 加的边数没到m,显然不行
return (void)puts("NO");
}
puts("YES");
for(int i=1;i<=acnt;i++)printf("%d ",ans[i]);
puts("");
}
}
namespace directed_euler{
int ans[maxn<<1],acnt=0;
int vis[maxn<<1]; // 注意 vis 记录的是边,双向边需开2倍
int in[maxn],out[maxn];
void euler_di(int x){
for(;head[x]!=-1;){
int curedge=head[x];
head[x]=ed[curedge].next;
if(!vis[curedge]){
vis[curedge]=1;
euler_di(ed[curedge].to);
ans[++acnt]=curedge;
}
}
}
void direct(){ // 有向图同理
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
int x=0,y=0;
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add(y,x);++out[x];++in[y];
}
for(int i=1;i<=n;i++) // 只需要判断出度入度是否相等
if(in[i]!=out[i])return (void)puts("NO");
euler_di(x);
if(acnt!=m)return (void)puts("NO");
puts("YES");
for(int i=1;i<=acnt;i++)printf("%d ",ans[i]);
puts("");
}
}
int main(){
int kas;
scanf("%d",&kas);
if(kas==1)undirected_euler::undirect();
else directed_euler::direct();
return 0;
}
Luogu2731
无向图欧拉路径,保证有解
利用map处理了重边,如果更快可以用链式前向星做到 \(O(1)\)
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long ll;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5 + 5;
// O(n+m)
int n,m;
vector<int>g[maxn];
map<pii,int>mp;
namespace un_euler_path{
int de[maxn];
int ans[maxn<<1],acnt=0;
int vis[maxn<<1]; // 注意 vis 记录的是边,双向边需开2倍
int hd[maxn];
void euler_un(int x){ // 当前搜到 x 号点
while(1){
for(;hd[x]<g[x].size() && !mp[mpr(x,g[x][hd[x]])];)++ hd[x];
if(hd[x] >= g[x].size())break;
int v = g[x][hd[x]];
-- mp[mpr(x, v)];
-- mp[mpr(v, x)];
++ hd[x];
euler_un(v);
}
ans[++ acnt] = x;
}
void undirect(){ // 如果未保证欧拉路径一定存在,还需要判断是否只有2个/0个 奇点,最终的路径top 是否为m
memset(hd,0,sizeof hd);
scanf("%d",&m);
int n = 500, st = 500;
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
g[x].push_back(y), g[y].push_back(x);
++de[x];++de[y];
++ mp[mpr(x, y)]; ++ mp[mpr(y, x)];
st = min(st, min(x, y));
}
for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
for(int i=1;i<=n;i++){
if(de[i]&1){
st = i;
break;
}
}
euler_un(st);
// if(acnt!=m) // 加的边数没到m,显然不行
// return (void)puts("NO");
for(int i=acnt;i>=1;i--)printf("%d\n",ans[i]);
}
}
int main(){
un_euler_path::undirect();
return 0;
}
Luogu1341
将每个字母对对应的两个字母连一条无向边,然后跑欧拉路径即可
判断无解的时候就是,如果有0个或2个奇点才可能有解,否则直接无解。然后跑欧拉路径看看
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long ll;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 3005;
vector<int>g[maxn];
map<pii,int>mp;
int de[maxn];
int ans[maxn<<1];
int acnt=0;
namespace un_euler_path{
int vis[maxn<<1]; // 注意 vis 记录的是边,双向边需开2倍
int hd[maxn];
void dfs(int x){ // 当前搜到 x 号点
while(1){
for(;hd[x]<g[x].size() && !mp[mpr(x,g[x][hd[x]])];)++ hd[x];
if(hd[x] >= g[x].size())break;
int v = g[x][hd[x]];
-- mp[mpr(x, v)];
-- mp[mpr(v, x)];
++ hd[x];
dfs(v);
}
ans[++ acnt] = x;
}
}
signed main(){
int m;
scanf("%d",&m);
set<char>ap, tr;
for(int i=1;i<=m;i++){
char s[5];scanf("%s",s + 1);
g[s[1]].push_back(s[2]);
g[s[2]].push_back(s[1]);
++ de[s[1]]; ++ de[s[2]];
ap.insert(s[1]), ap.insert(s[2]);
++ mp[mpr(s[1], s[2])]; ++ mp[mpr(s[2], s[1])];
}
for(char q : ap){
sort(g[q].begin(), g[q].end());
if(de[q] & 1){
tr.insert(q);
}
}
if(tr.size() != 0 && tr.size() != 2)puts("No Solution");
else{
char start = tr.size() == 0 ? *ap.begin() : *tr.begin();
un_euler_path::dfs(start);
for(int i=acnt;i>=1;i--)printf("%c",ans[i]);puts("");
}
return 0;
}
Luogu1127
每个单词首字母和尾字母连一条有向边,跑欧拉路径,把边权设成对应的单词的编号,然后记录答案的时候记录边权而非点
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long ll;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 1005;
int n;
string s[maxn];
struct node{
int to;
int vid;
};
vector<node>g[maxn];
int in[maxn];
int st[maxn], tp;
namespace di_euler_path{ // 有向图欧拉路径
int hd[maxn];
void dfs(int id,int lst=0){
for(int &i = hd[id];i<g[id].size();){i++;dfs(g[id][i-1].to, g[id][i-1].vid);}
st[++ tp] = lst;
}
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)cin >> s[i];
sort(s+1,s+n+1);
set<int>all;
for(int i=1;i<=n;i++){
node now;
now.to = s[i][s[i].size() - 1];
now.vid = i;
int from = s[i][0];
g[from].push_back(now);
++ in[now.to];
all.insert(s[i][0]), all.insert(s[i][s[i].size() - 1]);
}
int lst = 0;
for(int i : all){
if(abs((int)g[i].size() - in[i]) > 1){puts("***");return 0;}
if(g[i].size() > in[i]){
if(lst){puts("***");return 0;}
else lst = i;
}
}
di_euler_path::dfs(lst ? lst : s[1][0]);
if(tp != n+1)puts("***");
else{
reverse(st+1,st+tp+1);
for(int i=2;i<=tp;i++)cout<<s[st[i]]<<".\n"[i == tp];
}
return 0;
}
ZROJ138
首先变成无向图,因为奇点一定有偶数个,每两个奇点之间连一条边,然后跑欧拉回路,最后答案一定是偶点个数,方案就是跑欧拉回路时记录的
因为如果全图都是偶点的话,一定有欧拉回路,欧拉回路是一条有向的回路,每个点一定入度=出度,对于那些没有加边的点这就是符合要求的
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long ll;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 5e5+5;
int n,m;
int de[maxn];
vector<pii>g[maxn];
map<pii,int>mp;
int hd[maxn];
int ans[maxn], acnt = 0, vis[maxn];
//int qw[maxn],qwq;
// 无向图欧拉回路
void dfs(int x){ // 当前搜到 x 号点
vis[x] = 1;
while(1){
for(;hd[x] < g[x].size() && !mp[mpr(x, g[x][hd[x]].first)];)++ hd[x];
if(hd[x] >= g[x].size())break;
int cur = hd[x], u = g[x][cur].first, v = g[x][cur].second; // (x,u) in E
if(mp[mpr(x, u)]){
-- mp[mpr(x, u)]; -- mp[mpr(u, x)];
++ hd[x];
dfs(u);
if(v < 0)ans[-v] = 1;
}
}
// qw[++qwq] = x;
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
++ de[x], ++ de[y];
++ mp[mpr(x, y)];++ mp[mpr(y, x)];
g[x].push_back(mpr(y, i)), g[y].push_back(mpr(x, -i));
}
vector<int>odd;
int cnt = 0;
for(int i=1;i<=n;i++){
if(de[i]%2 == 0)++ cnt;
else odd.push_back(i);
}
for(int i=0;i<odd.size();i+=2){
int u = odd[i], v = odd[i+1];
// printf("%d %d\n",u,v);
++ mp[mpr(u, v)]; ++ mp[mpr(v, u)];
g[u].push_back(mpr(v, m+1));g[v].push_back(mpr(u, m+1));
}
printf("%d\n",cnt);
for(int i=1;i<=n;i++){
if(!vis[i])dfs(i);
}
// while(qwq)printf("%d ",qw[qwq--]);
for(int i=1;i<=m;i++)printf("%d",ans[i]);
return 0;
}