[NOIp2018] luogu P5022 旅行

题目描述

有一棵基环树 T T T,你初始在一个点上。每次可以从下列选项中选择一项执行:

  1. 沿着一条边走到一个没有访问过的点;
  2. 沿着一条边返回一个访问过的点。

你需要依此法访问所有的 N N N 个点。每个点被首次访问的顺序形成了一个序列,求这个序列字典序最小的那个。

Solution

由数据规模知,这大概是一个时间复杂度 O ( N 2 ) O(N^2) O(N2) 的程序。

先考虑树的情况。

对于一个节点 k k k,如果他有多个未访问儿子,那么每次走编号最小的那个永远是最优的。
所以我们建图的时候,先存下边,排序后再建图。这样就能保证我们找到的第一个答案就是最优答案。

for(int i=1;i<=m;++i){
	sx=read();sy=read();
	e[++len].x=sx;e[len].y=sy;
	e[++len].x=sy;e[len].y=sx;
}
sort(e+1,e+len+1);

这样我们就能拿到 60 分,时间复杂度 O ( M log ⁡ M + N ) O(M\log M+N) O(MlogM+N)


因为基环树删去环上一条边后退化成树,所以考虑枚举删除的边,然后转化成树的情况。时间复杂度 O ( M log ⁡ M + N 2 ) O(M\log M+N^2) O(MlogM+N2),期望得分 100。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXN=5010;

struct node{
	int x,y,next;
	node(){
		x=y=next=0;
	}
	friend bool operator<(const node a,const node b){
		if(a.x!=b.x) return a.x<b.x;
		return a.y<b.y;
	}
}e[MAXN+MAXN];
int len=0;
int n,m;
int sx,sy;
int first[MAXN];
int a[MAXN],H;
int ans[MAXN];
int fkx,fky;
int circle[MAXN];
int ST=0;
bool b[MAXN];
int bfind=0;
int fa[MAXN];
int findfa(int x){
	if(fa[x]==x) return x;
	return fa[x]=findfa(fa[x]);
}
void ins(int x,int y){
	e[++len].x=x;e[len].y=y;
	e[len].next=first[x];first[x]=len;
}
void dfs(int x,int fa,int h){
	for(int i=first[x];i;i=e[i].next){
		if(bfind) return;
		int y=e[i].y;
		if(fa==y) continue;
		circle[h]=i;
		if(b[y]){
			bfind=h;
			return;
		}
		b[x]=1;dfs(y,x,h+1);b[x]=0;
	}
}
void work(int x,int fa){
	a[H]=x;
	for(int i=first[x];i;i=e[i].next){
		int y=e[i].y;
		if((x==fkx&&y==fky)||(x==fky&&y==fkx)) continue;
		if(y==fa) continue;
		++H;
		work(y,x);
	}
}
void update(){
	for(int i=1;i<=n;++i){
		if(ans[i]<a[i]) return;
		if(a[i]<ans[i]) break;
	}
	for(int i=1;i<=n;++i)
		ans[i]=a[i];
}
inline int read(){
	int x=0; char c;
	do c=getchar(); while(c<'0'||c>'9');
	while(c>='0'&&c<='9')
		x=x*10+c-48,c=getchar();
	return x;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;++i)
		fa[i]=i;
	for(int i=1;i<=m;++i){
		sx=read();sy=read();
		e[++len].x=sx;e[len].y=sy;
		e[++len].x=sy;e[len].y=sx;
		int x=findfa(sx),y=findfa(sy);
		if(x!=y) fa[x]=y;
		else ST=y;
	}
	sort(e+1,e+len+1);
	memset(first,0,sizeof(first));
	for(int i=len;i>=1;--i){
		first[e[i].x]=i;
		if(e[i].x==e[i+1].x) e[i].next=i+1;
	}
	memset(ans,63,sizeof(ans));
	if(n==m){
		memset(b,0,sizeof(b));b[ST]=1;
		dfs(ST,0,1);
		for(int i=1;i<=bfind;++i){
			fkx=e[circle[i]].x;fky=e[circle[i]].y;
			if(i==1&&fkx!=e[circle[bfind]].y) continue;
			H=1;work(1,0);
			if(H==n) update();
		}
	}else{
		H=1;work(1,0);
		update();
	}
	for(int i=1;i<=n;++i)
		printf("%d ",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值