Codeforces-1605 D: Treelabeling

Codeforces-1605 D: Treelabeling

题目

题目传送门:Treelabeling

题目截图

在这里插入图片描述

样例描述

在这里插入图片描述

题目大意

  给定一个 n n n 个结点的树,小E和小S轮流移动棋子,小E第一个下,可以下在任意的结点上。每次移动棋子,从当前结点 u u u 移动到下一结点 v v v 需要满足三个条件:

  1. u ↔ v u \leftrightarrow v uv是树上的一条边
  2. v v v 没有被访问过
  3. u ⊕ v ≤ min ⁡ ( u , v ) u \oplus v \le \min(u,v) uvmin(u,v) ⊕ \oplus 代表异或

  当某人无法继续移动棋子时,他就会输。
  在每次开始前,小E可以给树上的每个结点重新编号,现在他想要让能够让第一次下就能让自己必定胜利的落棋点尽可能多,求如何给整棵树重编号。

题目解析

  本题需要直接想最优情况,然后可以发现最优情况总是存在,也是许多游戏问题常见的思考方式。
  首先我们先考虑最优情况,就是小E下第一步,小S就动不了了,极端下,整颗树都是这样的情况,如此一来,解是最优的。因为如果有任何一个可以走 2 2 2 个结点以上的连通分量,那么肯定有一个点的下一步是死胡同,小E的胜利结点就会比最优情况要少。
  现在检查能否构造出这种最优解。
  首先第一条与第二条是普通的树遍历需要的,可以先直接略过。第三条,不难发现在 u u u v v v 二进制最高位不相同的情况下, u ⊕ v > min ⁡ ( u , v ) u \oplus v \gt \min(u,v) uv>min(u,v),因为二进制最高位不相同,那么得到 u ⊕ v u \oplus v uv 的二进制最高位必然是 1 1 1,那么必定比 u , v u,v u,v中最小的那一个大。那么如果我们让相连的结点都有不同的最高位,就能得到最优解。
  这种相邻结点之间有不同属性的问题可以从黑白染色的方法上进行考虑。如果相邻结点染成不同的颜色,就变成了不同的颜色间有某种性质。于是,我们将所有白节点和黑结点的最高位强制其不同,就可以解出这道题。那么怎么让它们的最高位不同呢,可以采用二进制构造的方式。比如白结点在二进制下有 100101 0 ( 2 ) 1001010_{(2)} 1001010(2)个,那么我们将每个 1 1 1 的位置当成最高位,就会有 [ 1 0 ( 2 ) , 1 1 ( 2 ) ] [10_{(2)},11_{(2)}] [10(2),11(2)], [ 100 0 ( 2 ) , 111 1 ( 2 ) ] [1000_{(2)}, 1111_{(2)}] [1000(2),1111(2)], [ 100000 0 ( 2 ) , 111111 1 ( 2 ) ] [1000000_{(2)}, 1111111_{(2)}] [1000000(2),1111111(2)],共 2 1 + 2 3 + 2 6 2^1+2^3+2^6 21+23+26 个,恰好就是白结点的个数。如果将剩余结点全部给黑节点,那么就能保证不会有黑白结点最高位相同的情况。
  当然如果白节点取的范围有一部分超过 n n n 的话,这种做法就不成立了,但我们可以让白节点是数量最小的那个,如此一来白节点数量 ≤ n / 2 \le n / 2 n/2 (这意味着白结点数量的最高位不会与 n n n 的最高位相等,至少低一位),也就意味着白结点所有 1 1 1 的位置当成最高位,取的范围都肯定能遍历所有情况且其中编号不超过 n n n
  如此我们通过构造最优解解出了这道题。

Code

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 7;
int num=1, h[maxn], color[maxn], ans[maxn];

struct E {
    int to, next;
}e[maxn * 2];

void add(int x, int y) {
   e[num].to = y; e[num].next = h[x]; h[x] = num++;
}

void  set_color(int x, int c) {
    color[x] = c;
    for(int i=h[x]; i; i=e[i].next) {
        int& v = e[i].to;
        if(color[v] == -1) set_color(v, c^1);
    }
}

int main(){
    int t, n, u, v;

    cin >> t;
    while(t--) {
        cin >> n;

        num = 1;
        memset(h, 0, (n + 1) * sizeof(int));
        memset(color, -1, (n + 1) * sizeof(int));
        memset(ans, 0, (n + 1) * sizeof(int));

        for(int i=1; i<=n-1; ++i) {
            cin >> u >> v;
            add(u, v); add(v, u);
        }
        set_color(1, 0);
        int cnt = 0;
        for(int i=1; i<=n; ++i)
            if(color[i] == 0) ++cnt;
        if (cnt > n-cnt) {
            cnt = n-cnt;
            for(int i=1; i<=n; ++i) color[i] ^= 1;
        }
        int black = 1, white = 1;

        for(int i=1; i<=n; i<<=1) {
            if(i & cnt) {
                int it = (i << 1);
                for(int j=i; j<it; ++j) {
                    while (white <= n && color[white] != 0) ++white;
                    ans[white++] = j;
                }
            } else {
                int it = min(i<<1, n+1);
                for(int j=i; j<it; ++j) {
                    while (black <= n && color[black] != 1) ++black;
                    ans[black++] = j;
                }
            }
        }

        for(int i=1; i<=n; ++i)
            cout << ans[i] << (i==n?'\n':' ');
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值