NOIP2018D2T1旅行

5 篇文章 0 订阅
2 篇文章 0 订阅

题目描述

*注:由于本题是个人A的第一道提高组蓝(luogu)题,所以我将把做这道题的解题思想详细记录下来一遍复习.

普通版P5022

变态 加强版P5049

(目前加强过不了TAT)

小Y是一个爱好旅行的OIer.她来到 X 国,打算将各城市都玩一遍.

小Y了解到, X国的n个城市之间有m条双向道路.每条双向道路连接两个城市.不存在两条连接同一对城市的道路,也不存在一条连接一个城市和它本身的道路.并且,从任意一个城市出发,通过这些道路都可以到达任意一个其他城市.小 Y 只能通过这些 道路从一个城市前往另一个城市。

小Y的旅行方案是这样的:任意选定一个城市作为起点,然后从起点开始,每次可以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市.当小Y回到起点时,她可以选择结束这次旅行或继续旅行.需要注意的是,小Y要求在旅行方案中,每个城市都被访问到。

为了让自己的旅行更有意义,小Y决定在每到达一个新的城市(包括起点)时,将 它的编号记录下来.她知道这样会形成一个长度为n的序列.她希望这个序列的字典序最小,你能帮帮她吗? 对于两个长度均为n的序列A和B,当且仅当存在一个正整数x,满足以下条件时, 我们说序列A的字典序小于B。

  • 对于任意正整数 1 ≤ i < x {1≤i<x} 1i<x,序列A的第i个元素 A i {A_i} Ai,
  • 和序列B的第i个元素 B i {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
说明/提示
【数据规模与约定】

对于 100 % {100\%} 100%的数据和所有样例, 1 ≤ n ≤ 50001 且 m = n − 1 或 m = n {1 \le n \le 50001且 m = n − 1或 m = n} 1n50001m=n1m=n

对于不同的测试点, 我们约定数据的规模如下:
在这里插入图片描述
解题思路

情况一: m = n − 1 {m=n-1} m=n1,情况二: m = n {m=n} m=n.
情况一解法: ( 60 p t s ) {(60pts)} (60pts)时间复杂度: O ( n   l o g n ) {O(n~logn)} O(n logn)
(实际上是排序加了复杂度,不然都是O(n)的 )
这种情况所遍历的图显然是棵树,所以我们先用邻接表存图,然后根据贪心思想从一开始依次遍历(优先走小的连边),这里涉及邻接表连边的排序.(这里建议用库函数的(可以直接排),而手写的邻接表排序得提前,而且排的方向是好像是反的(具体看代码(玄♂学 ))).再在遍历的过程中加入一个数组储存遍历顺序即可.

#include<bits/stdc++.h>
#define N 500005
#define in read()
using namespace std;

int n,m,tot,k;
int fi[N],nxt[2*N],to[2*N],ans[N];
bool ju[N];
struct zb{
int x,y;}a[2*N];//开两倍否则可能会爆

inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch^48);ch=getchar();}
	return i*f;
}

inline void lian(int u,int v)
{
	nxt[++tot]=fi[u];
	fi[u]=tot;
	to[tot]=v;
}

inline bool cmp(const zb &a,const zb &b)//*这里我想了半天,但时在不明白为何从大到小.
{
	return a.y>b.y;
}

inline void Dfs1(int u)
{
	ju[u]=1;
	ans[++k]=u;
	for(int i=fi[u];i;i=nxt[i])
	{
		int v=to[i];
		if(ju[v])continue;
		ju[v]=1;
		Dfs1(v);
	}
	return;
}

int main()
{
	n=in,m=in;
	for(int i=1;i<=m;i++)
	{
		a[i].x=in,a[i].y=in;
		a[i+m].x=a[i].y,a[i+m].y=a[i].x;
	}//双向都要存.
	sort(a+1,a+2*m+1,cmp);//必须先排,不然连边之后很难操作(反正我不会)
	for(int i=1;i<=2*m;i++)
	lian(a[i].x,a[i].y);
	if(n==m+1)
	{
		Dfs1(1);
		for(int i=1;i<=n;i++)
		printf("%d ",ans[i]);
	}
	else printf("orz I can not do it!\n")
	return 0;
}

然后妥妥的60分就到手了.

情况二解法: ( 100 p t s ) & & 加 强 ( 80 p t s ) {(100pts)\&\&加强(80pts)} (100pts)&&(80pts)时间复杂度: O ( n 2 ) {O(n^2)} O(n2)
这种情况显然有且只有一个环(找环上的点用拓扑排序),而且我们发现每次遍历完所有点肯定有一条边是可以不用走的,而且这条边一定在环上.所以我们先把环上的所有点找出来,然后枚举每一条环上的边并删去,再以此Dfs遍历一遍,若所得的得的字典序更小则更新答案即可.
(拓扑在图论是个好东西qwq,强烈建议大家没学得去学学!)

#include<bits/stdc++.h>
#define N 500005
#define in read()
using namespace std;

int n,m,tot,k,dt1,dt2;
int fi[N],nxt[2*N],to[2*N],ans[N],res[N],rd[N];
bool ju[N],use[N];
struct zb{
int x,y;}a[2*N];

inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch^48);ch=getchar();}
	return i*f;
}//快读加速

inline void lian(int u,int v)
{
	nxt[++tot]=fi[u];
	fi[u]=tot;
	to[tot]=v;
}

inline bool cmp(const zb &a,const zb &b)
{
	return a.y>b.y;
}

inline void work()
{
	if(!ans[1])//第一次要特判
	{	
		for(int j=1;j<=n;j++)
		ans[j]=res[j];
		return;
	}
	for(int i=1;i<=n;i++)
	{
		if(ans[i]==res[i])continue;
		if(res[i]>ans[i])return;
		for(int j=1;j<=n;j++)
		ans[j]=res[j];
		return;
	}
}

inline void Dfs1(int u)
{
	ju[u]=1;
	ans[++k]=u;
	for(int i=fi[u];i;i=nxt[i])
	{
		int v=to[i];
		if(ju[v])continue;
		ju[v]=1;
		Dfs1(v);
	}
	return;
}

inline void Dfs2(int u)
{
	res[++k]=u;
	ju[u]=1;
	for(int i=fi[u];i;i=nxt[i])
	{
		int v=to[i];
		if(ju[v]||(u==dt1&&v==dt2)||(u==dt2&&v==dt1))continue;
		ju[v]=1;
		Dfs2(v);
		ju[v]=0;//注意这里是环所以要回溯!
	}
	ju[u]=0;
	return;
}

inline void topu()//拓扑排序,处理双向图时入度为1就进队
{
	queue<int>q;
	for(int i=1;i<=n;i++)
	{
		if(rd[i]==1){
			use[i]=1;
			q.push(i);
		}
	}
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=fi[x];i;i=nxt[i])
		{
			int y=to[i];
			rd[y]--;
			if(rd[y]==1){
				use[y]=1;
				q.push(y);
			}
		}
	}
	return;
}

int main()
{
	n=in,m=in;
	for(int i=1;i<=m;i++)
	{
		a[i].x=in,a[i].y=in;
		a[i+m].x=a[i].y,a[i+m].y=a[i].x;
		rd[a[i].x]++,rd[a[i].y]++;
	}
	sort(a+1,a+2*m+1,cmp);
	for(int i=1;i<=2*m;i++)
	lian(a[i].x,a[i].y);
	if(n==m+1)
	{
		Dfs1(1);
		for(int i=1;i<=n;i++)
		printf("%d ",ans[i]);
	}
	else
	{
		topu();
		for(int i=1;i<=m;i++)
		{
			int x=a[i].x,y=a[i].y;
			if(use[x]||use[y])continue;//不在环上就跳过.
			dt1=x,dt2=y,k=0;//用变量代替邻接矩阵表示被删边可大大节约空间
			Dfs2(1);
			work();//更新答案
		}
		for(int i=1;i<=n;i++)
		printf("%d ",ans[i]);
	}
	return 0;
}

然后交上此代码就可以A了!
完结撒花!
更优算法待更新…

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
《信息学奥赛一本通 NOIP500 第1部分》是一本参考书籍,其主要目的是为了帮助准备参加NOIP(全青少年信息学奥林匹克的省级选拔赛)的学生进行备考。这本书的内容丰富多样,涵盖了计算机科学和编程的各个方面。 在这本书的第1部分,主要介绍了NOIP500比赛的背景和要求。它首先详细解释了NOIP500的含义和意义,以及为什么要参加这样的比赛。它还介绍了NOIP500的考试形式和内容,包括必考的算法、数据结构和编程语言等。此外,它还介绍了评分标准和考试日期等重要信息。 第1部分还包括了一些备考的重要指导和技巧。它解释了如何正确准备和安排备考时间,以及如何理解和解答常见的考试题型。此外,书中还提供了一些实用的练习题和例子,帮助学生加深对知识点的理解和掌握。 这本书的语言简洁明了,结构清晰,非常适合初学者使用。它通过大量的例子和习题,帮助学生从基础知识开始逐步提高。此外,书中还附有一些重要的参考资料和学习资源,让学生能够更加全面地了解和学习计算机科学和编程。 总体而言,《信息学奥赛一本通 NOIP500 第1部分》是一本非常有价值的参考书籍,它能够为准备参加NOIP500的学生提供全面和系统的知识,帮助他们在竞赛中取得良好的成绩。对于对信息学感兴趣的学生,这本书也是一本很好的自学教材。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liaoxiyan123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值