Baltic Olympiad in Informatics 2020, Day 2. B1. Village (Minimum)(思维/贪心)

原题链接:B1. Village (Minimum)


题目大意:


给出一个 n n n 个点 n − 1 n - 1 n1 条边的树,每一条边的长度为 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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值