Farmer John每年有很多栅栏要修理。他总是骑着马穿过每一个栅栏并修复它破损的地方。
John是一个与其他农民一样懒的人。他讨厌骑马,因此从来不两次经过一个栅栏。你必须编一个程序,读入栅栏网络的描述,并计算出一条修栅栏的路径,使每个栅栏都恰好被经过一次。John能从任何一个顶点(即两个栅栏的交点)开始骑马,在任意一个顶点结束。
每一个栅栏连接两个顶点,顶点用1到500标号(虽然有的农场并没有500个顶点)。一个顶点上可连接任意多(>=1)个栅栏。两顶点间可能有多个栅栏。所有栅栏都是连通的(也就是你可以从任意一个栅栏到达另外的所有栅栏)。
你的程序必须输出骑马的路径(用路上依次经过的顶点号码表示)。我们如果把输出的路径看成是一个500进制的数,那么当存在多组解的情况下,输出500进制表示法中最小的一个 (也就是输出第一位较小的,如果还有多组解,输出第二位较小的,等等)。
输入数据保证至少有一个解。
第1行: 一个整数F(1 <= F <= 1024),表示栅栏的数目
第2到F+1行: 每行两个整数i, j(1 <= i,j <= 500)表示这条栅栏连接i与j号顶点。
输出应当有F+1行,每行一个整数,依次表示路径经过的顶点号。注意数据可能有多组解,但是只有上面题目要求的那一组解是认为正确的。
9
1 2
2 3
3 4
4 2
4 5
2 5
5 6
5 7
4 6
1
2
3
4
2
5
4
6
5
7
题目翻译来自NOCOW。
USACO Training Section 3.3
欧拉路径模板
图中的度:所谓顶点的度(degree),就是指和该顶点相关联的边数。
在有向图中,度又分为入度和出度,在某顶点的入度和出度的和称为该顶点的度。
入度 (in-degree) :以某顶点为弧头,终止于该顶点的弧的数目。
出度 (out-degree) :以某顶点为弧尾,起始于该顶点的弧的数目。
桥边:在边集中,如果去掉某条边使得剩下的图不连通,则称这条边为桥边。
欧拉路径(Eulerian path):
每条边恰好走一次,但是不要求回到起始点,即一笔画
欧拉回路(Eularian cycle):
每条边恰好走一次,并能回到出发点的路径,即在欧拉路径的基础上要求始末点重合
如何判断图是否有欧拉路径或者欧拉回路呢?
一、无向图
一个无向图存在欧拉路径,当且仅当该图所有顶点的度数为偶数或者除了两个度数为奇数外其余的全是偶数。
二、有向图
一个有向图存在欧拉路径,当且仅当该图所有顶点的度数为零或者一个顶点的出度比入度大1(起始点),另一个顶点的出度比入度大1(终止点),其他顶点的度数为0。
一、无向图
每个顶点的度数都是偶数,则存在欧拉回路。
二、有向图
每个节顶点的入度都等于出度,则存在欧拉回路。
Fluery算法和Hierholzer算法。
Fleury算法详解
Hierholzer算法详解
后面一种算法无论是编程复杂度还是时间复杂度好像都比前种算法复杂度更优,但前者的应用广泛性好像比后者更高。
Fleury算法(代码长、效率低)我的程序在Luogu上TLE+RE
粗略分析一下,由于算法要经过每条边,所以时间必然是 Ω(E) Ω ( E ) 。
在最坏情况下,在每个节点处进行一次 DFS,节点会重复走所以以边计算
时间复杂度 O(E(E+V)) O ( E ( E + V ) )
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 510
using namespace std;
int x,y,n,m,i,a[N][N],ans[N];
stack<int> s;
bool dfs(int x)
{
s.push(x);
if(!a[x][0])
{
bool bz=1;
for(int i=1;i<=n;i++)
{
if(i==x) continue;
if(a[i][0])
{
bz=0;
break;
}
}
if(!bz) s.pop();
return bz;
}
for(int j=1;j<=n;j++) if(a[x][j])
{
a[x][j]--,a[j][x]--;
a[x][0]--,a[j][0]--;
if(dfs(j)) return 1;
else
{
a[x][j]++,a[j][x]++;
a[x][0]++,a[j][0]++;
}
}
s.pop();
return 0;
}
int main()
{
scanf("%d",&m);
fo(i,1,m)
{
scanf("%d%d",&x,&y);
n=max(n,max(x,y));
a[x][y]++,a[y][x]++;
a[x][0]++,a[y][0]++;
}
fo(i,1,n) if(a[i][0]&1) break;
if(i>n) dfs(1); else dfs(i);
while(!s.empty())
{
ans[++ans[0]]=s.top();
s.pop();
}
fd(i,ans[0],1) printf("%d\n",ans[i]);
}
Hierholzer算法(代码短、效率高)
在 DFS 的过程中不用恢复边,靠出栈记录轨迹。
时间复杂度 O(E) O ( E )
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define M 1025
#define N 510
using namespace std;
int i,x,y,n,m,first,cnt[N],a[N][N];
stack<int> s;
void dfs(int x)
{
for(int i=1;i<=n;i++)
if(a[x][i]) a[x][i]--,a[i][x]--,dfs(i);
s.push(x);
}
int main()
{
freopen("fence.in","r",stdin);
freopen("fence.out","w",stdout);
scanf("%d",&m);
fo(i,1,m)
{
scanf("%d%d",&x,&y);
n=max(n,max(x,y));
a[x][y]++,a[y][x]++;
cnt[x]^=1,cnt[y]^=1;
}
fo(i,1,n) if(cnt[i])
{
first=i;
break;
}
if(first) dfs(first); else dfs(1);
while(!s.empty()) printf("%d\n",s.top()),s.pop();
}