P2731 骑马修栅栏 Riding the Fences
题目背景
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行,每行一个整数,依次表示路径经过的顶点号。注意数据可能有多组解,但是只有上面题目要求的那一组解是认为正确的。
好气啊,我觉得这个欧拉路求法及其难以接受。。
欧拉路:从一个点出发,遍历每一个边刚好一次,到达任意一个点的路径称为欧拉路。
若起点和终点不相等,则起点和终点的度数为奇数;
若起点和终点相等,则称之为欧拉回路,整个无向连通图的每个点的度数都为偶数。
求法:从一个合法的起点产生一颗搜索树,对除起点和终点的(在欧拉回路中我们假装把它们分开考虑)每一个点都可以从一个最早的边进入,然后从一个最晚的边出去,我们忽略这两条边,对这个点就产生了一个递归的子问题,相当于从这个点产生了一个子搜索树。
当搜索树产生的点到达搜索树的起点的时候,我们不对这条路径进行存储,因为路径上的某些点可能产生的搜索树可能因为遍历顺序的问题还没有被扩展,而是当回退的时候,我们就用栈存储这条路径,这就保证了回退路径上所有的点都已经对自己的搜索树进行了扩展。对于每一颗搜索树的出边和入边两两进行配对,倒序着形成了一个个环。
倒着存储的核心,确保路径上每一点自己的子问题已经解决
为了保证复杂度,我们用前向星遍历边之后,得将head数组向后置,以便减少不必要的遍历。同时,也要判断每条边是否被遍历,因为是双向边。
对于此题,如果要保证字典序,则每个点优先遍历合法的最小的点,这样小的点就后进栈,当倒序输出栈的时候,自然也就是最小字典序了。
Code:
#include <cstdio>
#include <iostream>
const int N=502;
const int M=2050;
int g[N][N],head[N],Next[M],to[M],cnt=1,S=N,m,in[N];
void add(int u,int v)
{
Next[++cnt]=head[u];to[cnt]=v;head[u]=cnt;
}
void init()
{
scanf("%d",&m);
int u,v;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
if(u>v) std::swap(u,v);
g[u][v]++;
in[v]++,in[u]++;
S=S<u?S:u;
}
for(int i=500;i;i--)
if(in[i]&1)
S=i;
for(int i=500;i;i--)
for(int j=500;j;j--)
{
while(g[i][j]--)
add(i,j),add(j,i);
}
}
int s[M],tot,vis[M];
void dfs(int now)
{
for(int i=head[now];i;i=Next[i])
{
if(!vis[i])
{
vis[i]=1;
vis[i^1]=1;
head[now]=Next[i];
dfs(to[i]);
}
}
s[++tot]=now;
}
void work()
{
dfs(S);
for(int i=tot;i;i--)
printf("%d
",s[i]);
}
int main()
{
init();
work();
return 0;
}
2018.7.4