目录
显示
题目链接
https://www.luogu.org/problem/P5022
题解
对于树的情况,我们 DFS 直接贪心先走编号小的节点即可。时间复杂度是 \(O(n)\) 的。
问题在于基环树。我们先找出环,然后枚举断掉环上的某一条边,这样就转化为了树上的问题。
时间复杂度是 \(O(n^2)\) 的,因为 \(n \leq 5000\),所以可以轻松通过。
实现的时候,为了确保我们走编号小的节点,采用了 vector 来存边,将每个点的所有可达点排序,从编号小的点先开始遍历即可。
还有一个细节:如果当前遍历序列已经不是最优,直接回溯即可,这样可以稍微提高程序的实际运行效率。(然而加强版还是过不去)
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int n,m;
vector<int> a[5005];
namespace tree
{
int vis[5005],path[5005],cnt;
void dfs(int u)
{
vis[u]=1;
path[++cnt]=u;
int s=a[u].size();
for(int i=0;i<s;i++)
if(!vis[a[u][i]])dfs(a[u][i]);
}
void solve()
{
for(int i=1;i<=n;i++)
sort(a[i].begin(),a[i].end());
dfs(1);
for(int i=1;i<=n;i++)
printf("%d ",path[i]);
}
}
namespace loop
{
int sta[5005],loop[5005],vis[5005],tmp[5005],path[5005],top,siz,fib,cnt;
bool flag=false,is_best;
void init(int u,int fa)
{
vis[u]=1;
sta[++top]=u;
int s=a[u].size();
for(int i=0;i<s;i++)
if(!vis[a[u][i]])init(a[u][i],u);
else
{
if(fa!=a[u][i]&&!flag)
{
int num=top;
do
{
loop[++siz]=sta[num--];
}while(sta[num]!=a[u][i]);
loop[++siz]=a[u][i];
flag=true;
}
}
sta[top--]=0;
}
bool is_fib(int u,int v)//判断是否走了被断掉的边
{
if((u==loop[fib]&&v==loop[fib+1])||(v==loop[fib]&&u==loop[fib+1]))return true;
return false;
}
bool dfs(int u)
{
vis[u]=1,cnt++;
if(path[cnt]>u||fib==1)is_best=true;
if(!is_best&&path[cnt]<u)return false;//不是最优解直接回溯
path[cnt]=u;
int s=a[u].size();
for(int i=0;i<s;i++)
if(!is_fib(u,a[u][i])&&!vis[a[u][i]])
if(!dfs(a[u][i]))return false;
return true;
}
void solve()
{
init(1,0);//找环
loop[++siz]=loop[1];
for(int i=1;i<=n;i++)
sort(a[i].begin(),a[i].end());
for(int i=1;i<siz;i++)
{
memset(vis,0,sizeof(vis));
fib=i,cnt=0,is_best=false;
dfs(1);
}
for(int i=1;i<=n;i++)
printf("%d ",path[i]);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
a[u].push_back(v);
a[v].push_back(u);
}
if(m==n-1)tree::solve();
else loop::solve();
return 0;
}