时隔一年,终于做掉了这题。当时思维僵化,连这种贪心都想不出来,真的是菜的不行。
题意
有一棵树或者基环树,每个点有一个标号。
要求以一定的顺序深度优先遍历这棵树,使 dfs 序的字典序最小。
思路
因为字典序最小,只要有某一位的标号不够优了,后面的决策就不可能使他再次变为最优。所以显然可以贪心。
对于树的情况,dfs 从 1 号点开始,并且每次把一个点的所有儿子拖出来排序,从小到大遍历。
对于基环树的情况,考虑我们比树多了些什么条件。因为多了一条边,在 dfs 的时候就可以选择一条边不走。假装我们还是按照树上的贪心策略在 dfs ,然后我们考虑在什么情况下会选择舍弃一条边。
- 首先舍弃的边必须要在环上
- 舍弃完之后必须马上回溯,也就是说舍弃的这条边连接的是当前点的最大的儿子
- 回溯之后走到的第一个点的标号比舍弃的那个儿子要小
其实 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;
}