Codeforces-1605 D: Treelabeling
题目
题目传送门:Treelabeling
题目截图
样例描述
题目大意
给定一个 n n n 个结点的树,小E和小S轮流移动棋子,小E第一个下,可以下在任意的结点上。每次移动棋子,从当前结点 u u u 移动到下一结点 v v v 需要满足三个条件:
- u ↔ v u \leftrightarrow v u↔v是树上的一条边
- v v v 没有被访问过
- u ⊕ v ≤ min ( u , v ) u \oplus v \le \min(u,v) u⊕v≤min(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)
u⊕v>min(u,v),因为二进制最高位不相同,那么得到
u
⊕
v
u \oplus v
u⊕v 的二进制最高位必然是
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;
}