题意 :
- 给一个结点1-n的树,E先手,将棋子放在其中一个结点上,S后手,棋子每次移动有三个条件要满足:1.结点相邻;2.结点未被访问;3. 𝑢 ⊕ 𝑣 ≤ 𝑚 𝑖 𝑛 ( 𝑢 , 𝑣 ) 𝑢⊕𝑣≤𝑚𝑖𝑛(𝑢,𝑣) u⊕v≤min(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 w≤b,我们可以将这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;
}