NOIP 2018 提高组 复赛 第二天 第一题 旅行 travel 基环树 AC代码+60分代码(深搜dfs)

NOIP 2018 提高组 复赛 第二天 第一题 旅行  travel AC代码+60分代码(深搜dfs)

总目录详见:NOIP 提高组 复赛 试题 目录 信奥 历年

在线测评地址:https://www.luogu.com.cn/problem/P5022

基环树

基环树就是有n个点n条边的图,由于比树只出现了一个环,那么就称之为基环树了。

基环树结构仍然很简单,但比树要恶心得多一些。

1.AC代码

拓扑排序

处理无向图

可以找出环上的所有点。

之后入度>=2的点就是环上的点

断环法

每次断开环上的一条边跑一遍答案,然后取最大值。

适用于:数据较小,且环不会影响答案的题目

解题思路

这道题有可能是个基环树 我们先不考虑基环树。我们可以发现每次走字典序小的点就好了。我们可以排序一下就可以做到这点。

之后我们考虑基环树,我们暴力删边将基环树变为一棵普通的树。然后计算答案。

时间复杂度: O(n^2)

然后愉快的发现可以过

AC代码如下:

#include <cstdio>
#include <algorithm>
#define maxn 5010
using namespace std;
struct node{
	int to,next;
}e[maxn<<1];
struct node2{
	int u,v;
}a[maxn<<1];
int n,m,head[maxn],tot,rd[maxn],circle[maxn],cnt,ans[maxn],an,state[maxn],st,q[maxn],h,t;
bool map[maxn][maxn];
int cmp(node2 a,node2 b){
	return a.v>b.v;
} 
void add_edge(int u,int v){
	tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot,rd[v]++;	
}
void dfs(int u,int fa){
	int b,v;
	state[++st]=u;
	for(b=head[u];b;b=e[b].next){
		v=e[b].to;
		if(!map[u][v]&&v!=fa)dfs(v,u);
	}
}
void topsort(){//拓扑去除环外的节点
	int i,u,v,b;
	h=t=1;
	for(i=1;i<=n;i++)
		if(rd[i]==1)q[t]=i,t++;
	while(h<t){
		u=q[h],rd[u]=0;
		for(b=head[u];b;b=e[b].next){
			v=e[b].to;
			rd[v]--;
			if(rd[v]==1)q[t]=v,t++;
		}
		h++;
	}
}
void find(){//找环//记录环的每个点
	int i,x,b,v;
	for(i=1;i<=n;i++)
		if(rd[i]>1){x=i;break;}//找环的切入点
	do{//找环
		circle[++cnt]=x;
		rd[x]=1;
		for(b=head[x];b;b=e[b].next){
			v=e[b].to;
			if(rd[v]>1){x=v;break;}
		}
	}while(b);
	circle[++cnt]=i;
}
void judge(){//判断是否为更小字典序
	int flag=0,i;
	for(i=1;i<=n;i++)
		if(state[i]<ans[i]){flag=1;break;}
		else if(state[i]>ans[i]) return;//此句关键
	if(!flag)return;
	for(;i<=n;i++)ans[i]=state[i];
}
void solve(){
	int i;
	for(i=1;i<cnt;i++){//枚举删除的边
		map[circle[i]][circle[i+1]]=map[circle[i+1]][circle[i]]=1;
		st=0;
		dfs(1,0);
		judge();
		map[circle[i]][circle[i+1]]=map[circle[i+1]][circle[i]]=0;
	}
	for(i=1;i<=n;i++)printf("%d ",ans[i]);
}
int main(){
	int i,u,v;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		a[i].u=u,a[i].v=v;
		a[i+m].u=v,a[i+m].v=u;
	}
	sort(a+1,a+1+2*m,cmp);
	for(i=1;i<=2*m;i++)
		add_edge(a[i].u,a[i].v);
	if(m==n-1){//普通的树
		dfs(1,0);
		for(i=1;i<=n;i++)printf("%d ",state[i]);
		return 0;
	}
	for(i=1;i<=n;i++)ans[i]=5010;
	topsort();
	find();
	solve();
	return 0;
}

2.60分代码(深搜dfs)

 

 

针对如下数据进行编码

很明显,是一棵树,从城市1开始遍历,在树上遍历时,只需要注意遍历当前城市的孩子时,将孩子先遍历出来,按自小到大排序,再进行下一步的遍历即可。

60分代码(深搜dfs)如下

#include <cstdio>
#include <algorithm>
#define maxn 5010
using namespace std;
struct node{
	int to,next;
}e[maxn<<1];
int n,m,head[maxn],tot;
void add_edge(int u,int v){
	tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;	
}
void dfs(int u,int fa){
	int b,v,i;
	int a[maxn],cnt=0;
	printf("%d ",u);
	for(b=head[u];b;b=e[b].next){
		v=e[b].to;
		if(v!=fa)cnt++,a[cnt]=v;
	}
	sort(a+1,a+1+cnt);
	for(i=1;i<=cnt;i++){
		dfs(a[i],u);
	}
}
int main(){
	int i,u,v;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		add_edge(u,v),add_edge(v,u);
	}
	dfs(1,0);
	return 0;
}

60分代码(深搜dfs)改进如下

#include <cstdio>
#include <algorithm>
#define maxn 5010
using namespace std;
struct node{
	int to,next;
}e[maxn<<1];
struct node2{
	int u,v;
}a[maxn<<1];
int n,m,head[maxn],tot;
int cmp(node2 a,node2 b){
	return a.v>b.v;
} 
void add_edge(int u,int v){
	tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;	
}
void dfs(int u,int fa){
	int b,v;
	printf("%d ",u);
	for(b=head[u];b;b=e[b].next){
		v=e[b].to;
		if(v!=fa)dfs(v,u);
	}
}
int main(){
	int i,u,v;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		a[i].u=u,a[i].v=v;
		a[i+m].u=v,a[i+m].v=u;
	}
	sort(a+1,a+1+2*m,cmp);
	for(i=1;i<=2*m;i++)
		add_edge(a[i].u,a[i].v);
	dfs(1,0);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值