[CSAcademy]Find the Tree
[CSAcademy]Find the Tree
题目大意:
交互题。
有一棵\(n(n\le2000)\)个结点的树,但是你并不知道树的形态。你可以调用\({\rm query}(x,y,z)\)(其中\(x,y,z\)互不相同)得到与\(x,y,z\)三点距离之和最小的点\(t\)。要求你使用不超过\(25000\)次询问,求出树上的所有边。
保证树的形态随机。
思路:
一开始先随便找两个点\(x,y\),枚举第三个点\(z\),询问\({\rm query}(x,y,z)\),则返回的\(t\)一定是\(x,y\)链上的点,而枚举完所有的\(z\)后,链上的所有点都能被找出来。
对于链上的点\(c_1,c_2\),若\({\rm query}(x,c_1,c_2)=c_1\),则\(c_1\)更靠近\(x\),否则\(c_2\)更靠近\(x\)。这样,我们可以对链上结点排序,从而求出链上的每一条边。
而在上述询问的过程中,我们也可以顺便求出去掉链\(x,y\)后,每个结点\(z\)在哪个子树中。对每一个子树递归进行上述操作即可。
据说可以证明重心落在随机链上的概率\(>\frac12\),因此总的询问次数大约是\(\mathcal O(n\log n)\)的。
源代码:
#include<set>
#include<ctime>
#include<cstdio>
#include<cctype>
#include<vector>
#include<cstdlib>
#include<algorithm>
inline int getint() {
register char ch;
while(!isdigit(ch=getchar()));
register int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
const int N=2001;
std::vector<int> v[N];
std::vector<std::pair<int,int>> ans;
inline int query(const int &x,const int &y,const int &z) {
if(x==y) return x;
if(x==z) return x;
if(y==z) return y;
printf("Q %d %d %d\n",x,y,z);
fflush(stdout);
return getint();
}
void solve(const int &x) {
if(v[x].empty()) return;
std::vector<int> u;
u.swap(v[x]);
const int &y=u[rand()%u.size()];
std::set<int> set;
set.insert(x);
set.insert(y);
for(register unsigned i=0;i<u.size();i++) {
const int &z=u[i];
if(z==y) continue;
const int t=query(x,y,z);
set.insert(t);
if(t!=z) v[t].push_back(z);
}
std::vector<int> chain(set.begin(),set.end());
std::sort(chain.begin(),chain.end(),
[x](const int &y,const int &z) {
return query(x,y,z)==y;
}
);
for(register unsigned i=1;i<chain.size();i++) {
ans.emplace_back(chain[i-1],chain[i]);
}
for(int v:chain) solve(v);
}
int main() {
srand(time(NULL));
const int n=getint();
for(register int i=2;i<=n;i++) {
v[1].push_back(i);
}
solve(1);
puts("A");
for(auto e:ans) {
printf("%d %d\n",e.first,e.second);
}
return 0;
}