洛谷P5022 [NOIP2018 提高组] 旅行题解

探讨了如何使用DFS方法解决旅行者在给定城市网络中找到字典序最小的旅行序列问题,涉及树形和环形图的处理策略。
摘要由CSDN通过智能技术生成

原题目: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;
}
  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值