背景:
昨天晚上肝到好晚,今天中午补一下吧。
欧拉路径&欧拉回路:
定义:
如果图 G G G中的一个路径包括每个边恰好一次,则该路径称为欧拉路径 (Euler path) \text{(Euler path)} (Euler path)。
如果一个回路是欧拉路径,则称为欧拉回路 (Euler circuit) \text{(Euler circuit)} (Euler circuit)。
——摘自《百度百科》
判断的充要条件:
欧拉路径
图
G
G
G是连通的,无孤立点。
无向图奇点数为
0
0
0或
2
2
2,并且这两个奇点其中一个为起点另外一个为终点。
有向图,可以存在两个点,其入度不等于出度,其中一个出度比入度大
1
1
1,为路径的起点;另外一个入度比出度大
1
1
1,为路径的终点。
欧拉回路
图
G
G
G是连通的,无孤立点。
无向图奇点数为
0
0
0。
有向图每个点的入度必须等于出度。
求解:
题目传送门:https://www.luogu.org/problem/P2731
假设当前的图中存在欧拉路径,如何求解欧拉路径(或欧拉回路)?
具体做法是:找到起点(欧拉路径就是奇数点无向图是任意一个奇点,有向图是出度比入度大
1
1
1的奇点;欧拉回路的起点要枚举)后
dfs
\text{dfs}
dfs,每一次将遍历到的边删除,直到无法走通。若当前无法走通,加入栈中。将最后的栈反向输出即为结果。
这样的时间复杂度是:
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)。
如果你用一个
set
\text{set}
set维护删边的话,那么时间复杂度就是
Θ
(
n
log
n
)
\Theta(n\log n)
Θ(nlogn)。
代码:
(无向图)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,ma=0,mi=1000,top=0,st;
int a[2010][2010],du[2010],sta[2010];
void dfs(int x)
{
for(int i=mi;i<=ma;i++)
if(a[i][x])
{
a[i][x]--,a[x][i]--;
dfs(i);
}
sta[++top]=x;
}
int main()
{
int x,y;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&x,&y);
du[x]++,du[y]++;
a[x][y]++,a[y][x]++;
ma=max(ma,max(x,y)),mi=min(mi,min(x,y));
}
for(int i=mi;i<=ma;i++)
if(du[i]&1)
{
st=i;
break;
}
if(!st)
{
for(int i=mi;i<=ma;i++)
{
dfs(i);
if(sta[1]==sta[top]) break;
}
}
else
{
dfs(st);
}
for(int i=top;i>=1;i--)
printf("%d\n",sta[i]);
}
哈密顿路径&哈密顿回路:
网上的
blog
\text{blog}
blog太少了。
定义:
如果图 G G G中的一个路径包括每个点恰好一次,则该路径称为哈密顿路径 (Hamiltonian path) \text{(Hamiltonian path)} (Hamiltonian path)。
如果一个回路是哈密顿路径(此时起点经过两次),则称为哈密顿回路 (Hamiltonian circuit) \text{(Hamiltonian circuit)} (Hamiltonian circuit)。
——摘自《百度百科》
判断的充要条件:
哈密顿路径
NP
\text{NP}
NP问题,暴力搜索或状压。
哈密顿回路
Dirac
\text{Dirac}
Dirac定理:若无向图上有
n
n
n个点,每一个点的度都大于等于
⌈
n
2
⌉
\lceil\frac{n}{2}\rceil
⌈2n⌉,则一定存在哈密顿回路。
证明:
没看懂的证明。
选出两个点
u
,
v
u,v
u,v,剩下
n
−
2
n-2
n−2个点,因为每一个点的度都大于等于
⌈
n
2
⌉
\lceil\frac{n}{2}\rceil
⌈2n⌉,所以
d
u
+
d
v
≥
⌈
n
2
⌉
+
⌈
n
2
⌉
≥
n
d_u+d_v≥\lceil\frac{n}{2}\rceil+\lceil\frac{n}{2}\rceil≥n
du+dv≥⌈2n⌉+⌈2n⌉≥n,因此一定存在一个点连接
x
,
y
x,y
x,y,因此每两个点都相通。
求解:
题目传送门:
poj2438
\text{poj2438}
poj2438。
假设当前的无向图图中存在哈密顿回路,如何求解哈密顿回路?
[
1
]
.
[1].
[1].任意找两个相邻点
S
,
T
S,T
S,T且存在一条边
S
→
T
S→T
S→T。若存在边
x
→
S
x→S
x→S且
x
x
x不在路径
S
→
T
S→T
S→T上,则更新
S
=
x
S=x
S=x。这样我们就得到了一条从
S
→
T
S→T
S→T的最长的路径。
[
2
]
.
[2].
[2].若最后的存在一条边
T
→
S
T→S
T→S,则
S
−
T
S-T
S−T构成了一个回路,且每一个点都经过一次,此时为哈密顿回路。
[
3
]
.
[3].
[3].若
S
S
S与
T
T
T不相邻,可以构造出一个回路。设路径
S
→
T
S→T
S→T上有
k
+
2
k+2
k+2个节点,依次为
S
,
v
1
,
v
2
…
…
v
k
S,v_1,v_2……v_k
S,v1,v2……vk 和
T
T
T。可以证明存在节点
v
i
v_i
vi,
i
∈
[
1
,
k
)
i∈[1,k)
i∈[1,k),满足
v
i
v_i
vi与
T
T
T相邻,且
v
i
+
1
v_{i+1}
vi+1与
S
S
S相邻。证明方法与上面类似。找到了满足条件的节点
v
i
v_i
vi以后,就可以把原路径变成
S
→
v
i
→
T
→
v
i
+
1
→
S
S→v_{i}→T→v_{i+1}→S
S→vi→T→vi+1→S,即形成了一个回路。
有图有真相,就在这晒:
[
4
]
.
[4].
[4].在当前的回路中找到一个点,这个点有一条连向外面的边,那么从该点处把回路断开,就变回了一条路径,同时还可以将与之相邻的点加入路径。
重复步骤
[
2
]
,
[
3
]
,
[
4
]
[2],[3],[4]
[2],[3],[4],直到所有点都被遍历过。
证明略。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,s,t,u;
int ans[510];
bool bz[510],ma[510][510];
void find()
{
while(1)
{
bool flag=false;
for(int i=1;i<=n;i++)
if(!bz[i]&&ma[t][i])
{
bz[i]=true;
ans[++u]=i;
t=i;
flag=true;
break;
}
if(!flag) return;
}
}
void Swap(int x,int y)
{
while(x<y)
{
swap(ans[x],ans[y]);
x++,y--;
}
}
void work()
{
for(int i=1;i<=n;i++)
if(ma[s][i])
{
t=i;
break;
}
bz[s]=bz[t]=true;
ans[1]=s,ans[2]=t;
while(1)
{
find();
Swap(1,u);
swap(s,t);
find();
if(!ma[s][t])
for(int i=2;i<u-1;i++)
if(ma[t][ans[i]]&&ma[s][ans[i+1]])
{
Swap(i+1,u);
t=ans[i+1];
break;
}
if(u==n) return;
for(int j=1;j<=n;j++)
{
bool flag=true;
if(bz[j]) continue;
for(int i=2;i<u-1;i++)
if(ma[j][ans[i]])
{
s=ans[i-1];
t=j;
Swap(1,i-1);
Swap(i,u);
ans[++u]=j;
bz[j]=true;
flag=false;
break;
}
if(!flag) break;
}
}
}
int main()
{
int x,y;
while(scanf("%d %d",&n,&m)!=EOF,(n||m))
{
n<<=1;
s=1,u=2;
memset(bz,false,sizeof(bz));
memset(ma,true,sizeof(ma));
for(int i=1;i<=n;i++)
ma[i][i]=false;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
ma[x][y]=ma[y][x]=false;
}
work();
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\n");
}
}