「BalticOI 2020」村庄 (贪心)

Subtask1: Min

考虑链上的情况,最优解肯定是两两相邻的交换,如果还有多,就再多交换一次

因此树上的也是类似,实际上就是求解一个最小边覆盖问题,选择一条边就是交换边两端的点编号

可以$O(n)$贪心/dp求解树上最小边覆盖

Subtask2: Max

考虑理想的最优情况:对于任意一条边,我们要求它被经过次数尽可能多

如果这条边两端子树大小分别为$a,b$,则它被经过的最多次数显然是$2\min\{a,b\}$

考虑找到树的重心,以它为根,此时任意一颗真子树的大小$\leq \frac{n} {2}$

为了构造最优答案,只需要每棵子树的集合相互错开即可

一种简单的构造方法是:取$\text{dfs}$序,平移$\frac{n} {2}$即可得到解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=1e5+10,INF=1e9+10;

int n;
vector <int> G[N];
ll Min,Max;
int ans1[N],ans2[N];
int dep[N],fa[N],vis[N];
// 最小边覆盖贪心法
void pre_dfs(int u,int f){
ans1[u]=u,fa[u]=f;
for(int v:G[u]) if(v!=f) {
pre_dfs(v,u);
if(!vis[v]) vis[v]=vis[u]=1,swap(ans1[u],ans1[v]),Min+=2;
}
if(!vis[u] && !f) vis[u]=1,swap(ans1[u],ans1[G[u][0]]),Min+=2;
}

int A[N],C;
int mi=1e9,rt,sz[N];
// 找重心
void dfs(int u,int f){
sz[u]=1;
int ma=0;
for(int v:G[u]) if(v!=f) {
dfs(v,u);
cmax(ma,sz[v]),sz[u]+=sz[v];
Max+=2*min(n-sz[v],sz[v]);
}
cmax(ma,n-sz[u]);
if(mi>ma) mi=ma,rt=u;
}
// 遍历dfs序
void dfs_get(int u,int f) {
A[++C]=u;
for(int v:G[u]) if(v!=f) dfs_get(v,u);
}
int main(){
n=rd();
rep(i,2,n) {
int u=rd(),v=rd();
G[u].pb(v),G[v].pb(u);
}
pre_dfs(1,0),dfs(1,0),dfs_get(rt,0);
rep(i,1,n) ans2[A[i]]=A[(i+n/2-1)%n+1];
printf("%lld %lld\n",Min,Max);
rep(i,1,n) printf("%d ",ans1[i]);
puts("");
rep(i,1,n) printf("%d ",ans2[i]);
puts("");
}