1. 程式人生 > >【NOIP2018】【洛谷P5022】旅行【基環樹】

【NOIP2018】【洛谷P5022】旅行【基環樹】

題目大意:

題目連結:https://www.luogu.org/problemnew/show/P5022
給出一棵 n n 個點 n n n

1 n-1 條邊的圖,從任意點開始遍歷的方法的字典序。


思路:

很明顯,開始肯定是要在點 1 1 ,這樣才能保證字典序最小。
建圖時要先排序,保證鄰接表訪問的順序到達點是升序。也是為了保證字典序最小。
然後分類討論。


1.樹( n n n 1 n-1 邊)

從點 1

1 開始暴力跑,因為每次會跑到編號最小的節點,所以跑一邊的答案就是最終答案。


2.基環樹( n n n n 邊)

找到環,斷環,然後按樹跑就可以了。


但是這道題還是很噁心,打了我 2 h 2h
一開始用鄰接矩陣還 T T 了。。。
在這裡插入圖片描述


程式碼:

//程式碼醜見諒
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=5010;
int n,m,s,ans[N],a[N],tot,head[N];
bool vis[N],v[N],ok;

struct edge
{
	int next,to;
}e[N*2];

struct node
{
	int x,y;
}map[N*2];  //排序用

void add(int from,int to)
{
	e[++tot].to=to;
	e[tot].next=head[from];
	head[from]=tot;
}

void ask(int x,int fa,int xx,int yy)
{
	if (x==1) s=0;
	a[++s]=x;
	vis[x]=1;
	for (int i=head[x];~i;i=e[i].next)
	{
		int y=e[i].to;
		if (!vis[y])
			if ((x!=xx||y!=yy)&&(x!=yy||y!=xx))  //斷環
				ask(y,x,xx,yy);
	}
	vis[x]=0;
}

void check() //求最優答案
{
	for (int i=1;i<=n;i++)
		if (a[i]<ans[i]) break;
		else if (a[i]>ans[i]) return;
	memcpy(ans,a,sizeof(a));
}

void dfs(int x,int fa)
{
	if (v[x])  //找到環
	{
		ok=1;
		return;
	}
	v[x]=1;
	for (int i=head[x];~i;i=e[i].next)
		if (e[i].to!=fa) 
		{
			int y=e[i].to;
			dfs(y,x);
			if (ok)  //已經有環了
			{
				ask(1,0,x,y);
				check();
				return;
			}
		}
	v[x]=0;
}

bool cmp(node x,node y)
{
	if (x.x<y.x) return 1;
	if (x.x>y.x) return 0;
	if (x.y<y.y) return 0;
	return 1;
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&map[i].x,&map[i].y);
		map[i+m].x=map[i].y;
		map[i+m].y=map[i].x;
	}
	sort(map+1,map+1+m*2,cmp);
	for (int i=1;i<=2*m;i++)
		add(map[i].x,map[i].y);
	if (n>m)
	{
		ask(1,0,-1,-1);
		for (int i=1;i<=n;i++)
			printf("%d ",a[i]);
	}
	else
	{
		memset(ans,0x3f3f3f3f,sizeof(ans));
		dfs(1,0);
		for (int i=1;i<=n;i++)
			printf("%d ",ans[i]);
	}
	return 0;
}