题目来源
题目介绍
给定log2n次机会,去猜出树上的一个特殊的点。
解题思路
由给定的机会,很容易看出这是一个在树上二分题目,所以我们可以利用树的重心来保证每次可以去除树上一半的点,最后即可求出答案。
解题代码
cpp
void dfs(ll u, ll v) {
siz[u] = 1;
maxtree[u] = 0;
for (auto it : edge[u]) {
if (it == v || flag[it]) continue;
dfs(it, u);
siz[u] += siz[it];
maxtree[u] = max(siz[it], maxtree[u]);
}
maxtree[u] = max(maxtree[u], S - siz[u]);
if (maxtree[u] < maxtree[root]) root = u;
}
这段代码通过dfs求出siz数组和maxtree数组,siz数组是树上点的子树大小,maxtree数组是去除该点的后剩余子树之中节点个数最多的数量
cpp
ll check(ll x, ll y) {
cout << "? " << x << " " << y << "\n";
ll pp; cin >> pp;
return pp;
}
通过交互来获取下一步怎么做
cpp
ll dfs1(ll x) {
dfs(x, 0);
vector<ll> son;
for (auto it : edge[x]) {
if (!flag[it]) son.push_back(it);
}
sort(son.begin(), son.end(), [&](auto x, auto y) {return siz[x] > siz[y]; });
if (son.size() == 0) return x;
else if (son.size() == 1) {
ll pp = check(x, son[0]);
if (pp == 0) return x;
else return son[0];
}
else if (son.size() == 2) {
ll pp = check(son[0], son[1]);
if (pp == 1) return x;
if (pp == 0) {
S = siz[son[0]];
root = 0;
flag[x] = 1;
dfs(son[0], 0);
return dfs1(root);
}
else {
S = siz[son[1]];
root = 0;
flag[x] = 1;
dfs(son[1], 0);
return dfs1(root);
}
}
else {
ll pp = check(son[0], son[1]);
if (pp == 1) {
S = siz[son[2]] + 1;
root = 0;
flag[son[0]] = 1;
flag[son[1]] = 1;
dfs(x, 0);
return dfs1(root);
}
else if (pp == 0) {
S = siz[son[0]];
root = 0;
flag[x] = 1;
dfs(son[0], 0);
return dfs1(root);
}
else {
S = siz[son[1]];
root = 0;
flag[x] = 1;
dfs(son[1], 0);
return dfs1(root);
}
}
}
我们可以对重心x这个点进行下面四个处理
1:x度数为0:直接返回答案x。
2:x度数为1:使用check()询问x和该点即可知道答案,然后返回该值就行。
二分3:x度数为2:使用check()询问x的两个相邻的点,求出之后的重心,再进行dfs1()。
4:x度数为3:使用check()询问x的三个相邻的点中较大的两个。最小的那个连 通块加一,因为要算上x本身,最后根据check()返回值求出之后的重心,再进行dfs1()。
最后求出该题的答案
总结
该题主要利用二分的思想和树的重心去解决这道题目,总体不难,但是其中分类去跑下一次dfs1()的细节比较多。