Treelabeling 异或性质,位运算,染色法,二分图(2100)

在这里插入图片描述
题意 :

  • 给一个结点1-n的树,E先手,将棋子放在其中一个结点上,S后手,棋子每次移动有三个条件要满足:1.结点相邻;2.结点未被访问;3. 𝑢 ⊕ 𝑣 ≤ 𝑚 𝑖 𝑛 ( 𝑢 , 𝑣 ) 𝑢⊕𝑣≤𝑚𝑖𝑛(𝑢,𝑣) uvmin(u,v)。无法移动的人输。现在重排列这些结点的编号,要求E第一次下时就能保证E赢的结点数最多,输出一种重排列方案

思路 :

  • 大胆猜想,如果每一个点都无法走到其他点,即每一个点都是孤立的,那么E无论从哪个点出发都是必胜的,第一次下时就能保证E赢的结点数必然是最大的
  • 结论 :一棵树一定是一个二分图(可以黑白染色)
  • 我们的构造方案要满足 ∀ ( 𝑢 , 𝑣 ) ∈ 𝐸 , 𝑢 ∀(𝑢,𝑣)∈𝐸,𝑢 (u,v)E,u x o r xor xor 𝑣 > m i n ( 𝑢 , 𝑣 ) 𝑣>min(𝑢,𝑣) v>min(u,v),不妨设 𝑢 > 𝑣 𝑢>𝑣 u>v,考虑𝑢的二进制的最高位,不难发现原条件等价于在这一位上𝑣只能为0,即相邻的两个结点必须要满足最高位不同,即在二进制下的位数不同
  • 相邻结点之间不能到达(想到二分图),这种相邻结点之间有不同属性的问题可以从黑白染色法考虑,相邻结点染不同颜色
  • 设白色有w个,黑色有b个,且不失一般性地令 w ≤ b w \leq b wb,我们可以将这n个数分为个数为w和b的两个集合,要满足二进制下位数相同的被放在同一个集合中
  • 最高有效位是第0位的有 2 0 2^0 20个,最高有效位是第1位的有 2 1 2^1 21个…,因此,可以按二进制的最高位将标号1-n分为 l o g n logn logn组,最高有效位是第i位的记为第 l a b e l label label_ g r o u p i group_i groupi
  • 在原图上跑dfs染色法能得到二分图的两个分量,然后看如何分配标号的分组,取大小较小的一个分量,它的结点可以拆成多个 2 i 2^i 2i大小的组,大小为 2 i 2^i 2i的组刚好和 l a b e l label label_ g r o u p i group_i groupi对应(恰好构成二进制拆分),然后再把剩下的标号分配给另外一个分量的结点
  • 二进制拆分 :一个数一定可以被分为若干个 2 i 2^i 2i相加
#include <iostream>
#include <vector>

using namespace std;

const int N = 2e5 + 10;

int n;
vector<int> g[N];
int sum0, sum1;
int col[N], f[N], ans[N];

void dfs(int u, int fa, int c)
{
    col[u] = c;
    
    if (c) sum1 ++ ;
    else sum0 ++ ;
    
    for (int i = 0, v; i < g[u].size(); i ++ )
    {
        v = g[u][i];
        if (v == fa) continue;
        dfs(v, u, c ^ 1);
    }
}

int main()
{
    int _ = 1;
    scanf("%d", &_);
    
    while (_ -- )
    {
        scanf("%d", &n);
        
        for (int i = 1; i <= n; i ++ ) g[i].clear();
        
        for (int i = 0, u, v; i < n - 1; i ++ )
        {
            scanf("%d%d", &u, &v);
            g[u].push_back(v), g[v].push_back(u);
        }
        
        sum0 = sum1 = 0;
        dfs(1, 0, 0);       // 在原图上跑
        
        // 找较小的集合,且改变颜色,始终使较小的那个集合颜色为0
        if (sum0 > sum1)
        {
            swap(sum0, sum1);
            for (int i = 1; i <= n; i ++ ) col[i] ^= 1;
        }
        
        for (int i = 1; i <= n; i ++ )
        {
            int hb;             // 1-n分别在二进制下的最高有效位
            for (hb = 30; hb >= 0; hb -- )
                if (i >> hb & 1)
                    break;
            
            // 较小的那个集合被分为若干个大小为2^i的组
            if (sum0 >> hb & 1) f[i] = 0;       // 较小的那个集合的二进制表示在它的最高有效位是1,那么它应该被分到这个较小的集合
            else f[i] = 1;      // 剩余的都是较大的那个集合
        }
        
        int cnt0 = 1, cnt1 = 1;		// 从1开始
        for (int i = 1; i <= n; i ++ )
        {
            if (col[i] == 0)        // 这个结点是较小集合的颜色
            {
                while (f[cnt0] == 1) cnt0 ++ ;      // 找到cnt0使得f[cnt0] == 0
                ans[i] = cnt0 ++ ;      // 在这个原图的结点上放上应当放的颜色的第顺序个
            }
            else
            {
                while(f[cnt1] == 0) cnt1 ++ ;
                ans[i] = cnt1 ++ ;
            }
        }
        
        for (int i = 1; i <= n; i ++ ) printf("%d ", ans[i]);
        puts("");
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值