原题链接:B1. Village (Minimum)
题目大意:
给出一个 n n n 个点 n − 1 n - 1 n−1 条边的树,每一条边的长度为 1 1 1 。
现在你要构造一个方法,让每一个点都不在其原来的位置上,具体规则如下:
- 最初每个点的编号 v a l i = i val_i=i vali=i ,对任意两点 i , j i,j i,j 保证 v a l i ≠ v a l j val_i \neq val_j vali=valj 。
- 对于每一个点 i i i 来说最初的编号为 v a l i val_i vali,交换之后为 v a l i ′ val_i' vali′ 。要保证 v a l i ≠ v a l i ′ val_i \neq val_i' vali=vali′ ,即执行交换后得到的编号与最初的编号不同。
- 点 i i i 和点 j j j 执行一次交换,代价为 d i s ( i , j ) + d i s ( j , i ) dis(i,j)+dis(j,i) dis(i,j)+dis(j,i) 。其中 d i s ( x , y ) dis(x,y) dis(x,y) 代表两点之间的最短路径。
现在让你构造一个方案,在花费的代价最小的前提下,使得每个点编号都与最初不一样,输出最小花费和每个点交换后的编号 v i ′ v_i' vi′ 。
解题思路:
我们贪心的想:最小的交换代价就是相邻两点之间进行交换。
考虑到每个点都要执行一次交换,又因为这是一颗树,我们给每个点的交换都定一个顺序。
随便选取一个点为根,假设根为 1 1 1,然后和类似树形 d p dp dp 的方式从叶子开始自下而上交换。
具体来说,交换的方法如下:
- 假设我们当前在 u u u ,我们先优先递归下去,先考虑叶子节点 v v v ,这样,我们考虑 u u u 时, v v v 的子树已经交换完了。
- 回溯到 u u u 时,如果点 v v v 没有交换过(即 v a l v = v val_v = v valv=v)那么点 v a l v val_v valv 就和 v a l u val_u valu 交换。否则我们的点 v v v 已经执行了交换(即 v a l v ≠ v val_v \neq v valv=v )我们此时执行交换是没有意义的 。
- 回溯到根节点 u = 1 u=1 u=1 时,如果我们的子节点 v v v 全都交换过了(满足 v a l v ≠ v val_v \neq v valv=v),那么我们的根节点由于没有和任何一个子节点 v v v 进行交换,此时一定会满足 v a l u = u val_u=u valu=u 。我们任取一个点 v v v 执行一次交换即可。
考虑这样贪心交换的正确性:
- 由于每一次都是相邻点交换,一定满足最小代价的条件。
- 对于一个子树的根 u u u 来说,假设有多个点 v v v 满足 v a l v = v val_v=v valv=v,那么每次执行交换 v a l u val_u valu 和 v a l v val_v valv 的操作时,要么是原根节点编号 v a l u val_u valu 和其他子树 v a l v val_v valv 进行交换,要么就是两个子树的编号 v a l v 1 val_{v1} valv1 和 v a l v 2 val_{v2} valv2 进行交换。由于编号不会回到自己本身的子树中,那么一定不会在多次交换之后使得 v a l v = v val_v = v valv=v 。
(这种交换方式会使得按次序交换的节点构成一个环,然后环执行一次旋转,显然编号不会回到自身身上)
我们贪心的执行正确性是有保证的,我们直接贪心地交换即可。
时间复杂度: O ( n ) O(n) O(n)
AC代码:
#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;
using u64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;
const int N = 1e5 + 10;
vector<int> g[N];
int val[N], ans = 0;
void DFS(int u, int ufa) {
val[u] = u;
for (auto& v : g[u]) {
if (v == ufa) continue;
DFS(v, u);
//点v没交换就和点u交换
if (val[v] == v) {
ans += 2;
swap(val[u], val[v]);
}
}
}
void solve() {
int n;
cin >> n;
for (int i = 1, u, v; i <= n - 1; ++i) {
cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
//递归交换
DFS(1, 0);
//根节点判断
if (val[1] == 1) {
ans += 2;
swap(val[g[1][0]], val[1]);
}
cout << ans << '\n';
for (int i = 1; i <= n; ++i) {
cout << val[i] << ' ';
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1; //cin >> t;
while (t--) solve();
return 0;
}