【NOIP 2018】 旅行(贪心)

文章目录

时隔一年,终于做掉了这题。当时思维僵化,连这种贪心都想不出来,真的是菜的不行。

题意

有一棵树或者基环树,每个点有一个标号。

要求以一定的顺序深度优先遍历这棵树,使 dfs 序的字典序最小。

思路

因为字典序最小,只要有某一位的标号不够优了,后面的决策就不可能使他再次变为最优。所以显然可以贪心。

对于树的情况,dfs 从 1 号点开始,并且每次把一个点的所有儿子拖出来排序,从小到大遍历。

对于基环树的情况,考虑我们比树多了些什么条件。因为多了一条边,在 dfs 的时候就可以选择一条边不走。假装我们还是按照树上的贪心策略在 dfs ,然后我们考虑在什么情况下会选择舍弃一条边。

  1. 首先舍弃的边必须要在环上
  2. 舍弃完之后必须马上回溯,也就是说舍弃的这条边连接的是当前点的最大的儿子
  3. 回溯之后走到的第一个点的标号比舍弃的那个儿子要小

其实 2 和 3 算是同一个条件,也就是舍弃一条边之后能够得到更优的 dfs 序。而且一旦有一条边满足上述要求,我们马上就把他舍弃掉。

这样一来,贪心思路就很清晰了,代码也不长。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10, M = N<<1;
namespace Graph
{
	int h[N], ecnt, nxt[M], v[M];
	void clear(){ecnt = 1;}
	void add_dir(int _u, int _v){
	    v[++ecnt] = _v;
	    nxt[ecnt] = h[_u]; h[_u] = ecnt;
	}
	void add_undir(int _u, int _v){
	    add_dir(_u, _v);
	    add_dir(_v, _u);
	}
}
using namespace Graph;
int n, m, dgr[N];
bool vis[N], inl[N], magic;

template<class T>inline void read(T &x){
	x = 0; bool fl = 0; char c = getchar();
	while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
	while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
	if (fl) x = -x;
}

int find_loop(int u, int fa){
	vis[u] = 1;
	for (int i = h[u]; i; i = nxt[i]){
		if (v[i] == fa) continue;
		if (vis[v[i]]){
			inl[u] = 1;
			return v[i];
		}
		else{
			int tmp = find_loop(v[i], u);
			if (tmp == 0) return 0;
			if (tmp == -1) continue;
			inl[u] = 1;
			return (u == tmp ? 0 : tmp);
		}
	}
	return -1;
}

void dfs(int u, int pre){
	vis[u] = 1;
	printf("%d ", u);
	int son[dgr[u]], sn = 0;
	for (int i = h[u]; i; i = nxt[i])
		if (!vis[v[i]])
			son[sn++] = v[i];
	sort(son, son+sn);
	for (int i = 0; i < sn; ++ i){
		if (vis[son[i]]) continue;
		if (magic && i == sn-1 && inl[u] && inl[son[i]] && son[i] > pre){
			magic = 0;
			continue;
		}
		dfs(son[i], i+1 < sn ? son[i+1] : pre);
	}
}

int main()
{
	read(n); read(m);
	clear();
	for (int i = 1; i <= m; ++ i){
		int x, y;
		read(x); read(y);
		add_undir(x, y);
		dgr[x]++; dgr[y]++;
	}
	if (m == n){
		find_loop(1, 0);
		magic = 1;
	}
	memset(vis, 0, sizeof vis);
	dfs(1, n+1);
	puts("");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值