原题目:P5022 [NOIP2018 提高组] 旅行 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
NOIP2018 提高组 D2T1
题目描述
小 Y 是一个爱好旅行的 OIer。她来到 X 国,打算将各个城市都玩一遍。
小 Y 了解到,X 国的 n 个城市之间有 m 条双向道路。每条双向道路连接两个城市。 不存在两条连接同一对城市的道路,也不存在一条连接一个城市和它本身的道路。并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。小 Y 只能通过这些 道路从一个城市前往另一个城市。
小 Y 的旅行方案是这样的:任意选定一个城市作为起点,然后从起点开始,每次可 以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市。当小 Y 回到起点时,她可以选择结束这次旅行或 继续旅行。需要注意的是,小 Y 要求在旅行方案中,每个城市都被访问到。
为了让自己的旅行更有意义,小 Y 决定在每到达一个新的城市(包括起点)时,将 它的编号记录下来。她知道这样会形成一个长度为 n 的序列。她希望这个序列的字典序 最小,你能帮帮她吗? 对于两个长度均为 n 的序列 A 和 B,当且仅当存在一个正整数 x,满足以下条件时, 我们说序列 A 的字典序小于 B。
- 对于任意正整数 1≤i<x,序列 A 的第 i 个元素 Ai 和序列 B 的第 i 个元素 Bi 相同。
- 序列 A 的第 x 个元素的值小于序列 B 的第 x 个元素的值。
输入格式
输入文件共 m+1 行。第一行包含两个整数 ,n,m(m≤n),中间用一个空格分隔。
接下来 m 行,每行包含两个整数 u,v(1≤u,v≤n) ,表示编号为 u 和 v 的城市之 间有一条道路,两个整数之间用一个空格分隔。
输出格式
输出文件包含一行,n 个整数,表示字典序最小的序列。相邻两个整数之间用一个 空格分隔。
输入输出样例
输入 #1
6 5 1 3 2 3 2 5 3 4 4 6
输出 #1
1 3 2 5 4 6
输入 #2
6 6 1 3 2 3 2 5 3 4 4 5 4 6
输出 #2
1 3 2 4 5 6
题目分析:
要求字典序最小,且所有点都能遍历到,那么一定从1号点开始搜。我们先看m=n-1的情况,这时的图是一棵树,所以我们想到用dfs+回溯的方法递归每一个点,求最小值
60分dfs搜索(m==n-1)
#include <bits/stdc++.h>
using namespace std;
int n,m;
vector<int> v[5005];
int w[5005],ans[5005],x[5005],y[5005];
int be[5005],flag[5005],f[5005];
int find(int i)
{
if (f[i]==0) return i;
return be[i]=find(be[i]);
}
bool pd()//判断是否更新
{
int f=0;
for (int i=1;i<=n;i++)
{
if (ans[i]>w[i]) f=1;
if (f==0 && ans[i]<w[i]) return false;
}
return true;
}
void cp()//保存最小值
{
for (int i=1;i<=n;i++)
{
ans[i]=w[i];
}
return ;
}
void dfs1(int i,int t)
{
if (i==0) return ;//到树顶了
int fl=0;
if (t==n+1)//搜完了每一个点,判断
{
if (pd()) cp();
return ;
}
for (auto j:v[i])//遍历v[i][j]
{
if (!flag[j] && j<ans[t])
{
flag[j]=1;
w[t]=j;
be[j]=i;
dfs1(j,t+1);
be[j]=0;
w[t]=0;
flag[j]=0;
fl=1;
//标记,将第t个数赋值为j
}
}
if (fl==0) f[i]=1;
if (f[be[i]]) find(i);//搜自己的上一数
dfs1(be[i],t);
}
int main ()
{
int i,j;
memset(ans,0x3f,sizeof(ans));
cin>>n>>m;
for (i=1;i<=m;i++)
{
cin>>x[i]>>y[i];
v[x[i]].push_back(y[i]);
v[y[i]].push_back(x[i]);
//vector动态数组,也可以用链式前向星,将x[i],y[i]双向建边
}
for (i=1;i<=n;i++) sort(v[i].begin(),v[i].end());
//排序能到的每个点(求字典序最小所以先搜小的点)
w[1]=1;
flag[1]=1;
dfs1(1,2);
for (i=1;i<=n;i++)
{
cout<<ans[i]<<' ';
}
}
100分dfs搜索
分析:当m==n时,树上就有了一个环,那么我们是不是可以像上方一样用m==n-1的方式实现呢?这时我们就可以不搜一条边(视作删去),变成m==n-1的情况
//剪枝
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int n,m;
vector<int> g[N];
int flag[N],path[N],ans[N];
int cnt,state;
struct node
{
int u,v;
}edge[N];
void dfs1(int h)
{
for(auto i:g[h])
{
if(flag[i]==0)
{
flag[i]=1;
cout<<i<<" ";
dfs1(i);
}
}
}
void dfs2(int x,int u,int v)//不能搜索的边的两个端点u和v
{
flag[x]=1;
path[++cnt]=x;
if(state==0)//等于最优值时,继续搜索
{
if(path[cnt]>ans[cnt])//判断是否大于最优值,剪枝
{
state=1;
return ;
}
if(path[cnt]<ans[cnt])//已经小于最优值,可以更新
{
state=-1;
}
}
for(auto i:g[x])
{
if(!(flag[i]||(x==u&&i==v)||(x==v&&i==u)))//如果满足所有要求就继续搜索
{
dfs2(i,u,v);
}
}
}
int main()
{
int i,j,k;
cin>>n>>m;
for(i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
edge[i].u=u,edge[i].v=v;
}
for(i=1;i<=n;i++)//
{
sort(g[i].begin(),g[i].end());
}
if(m==n-1)//同上
{
cout<<1<<" ";
flag[1]=1;
dfs1(1);//搜索
}
else//有一个环
{
memset(ans,0x3f,sizeof(ans));
for(i=1;i<=n;i++)//枚举当前不能搜的边
{
cnt=0;//当前字典序中的点数
state=0;//标记字典是否更小
memset(flag,0,sizeof(flag));
dfs2(1,edge[i].u,edge[i].v);
if(cnt==n&&state==-1)//满足条件
{
memcpy(ans,path,sizeof(ans));
}
}
for(i=1;i<=n;i++)
{
cout<<ans[i]<<" ";
}
}
return 0;
}