[CSAcademy]Find the Tree
题目大意:
交互题。
有一棵(n(nle2000))个结点的树,但是你并不知道树的形态。你可以调用({ m query}(x,y,z))(其中(x,y,z)互不相同)得到与(x,y,z)三点距离之和最小的点(t)。要求你使用不超过(25000)次询问,求出树上的所有边。
保证树的形态随机。
思路:
一开始先随便找两个点(x,y),枚举第三个点(z),询问({ m query}(x,y,z)),则返回的(t)一定是(x,y)链上的点,而枚举完所有的(z)后,链上的所有点都能被找出来。
对于链上的点(c_1,c_2),若({ m query}(x,c_1,c_2)=c_1),则(c_1)更靠近(x),否则(c_2)更靠近(x)。这样,我们可以对链上结点排序,从而求出链上的每一条边。
而在上述询问的过程中,我们也可以顺便求出去掉链(x,y)后,每个结点(z)在哪个子树中。对每一个子树递归进行上述操作即可。
据说可以证明重心落在随机链上的概率(>frac12),因此总的询问次数大约是(mathcal O(nlog 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
",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
",e.first,e.second);
}
return 0;
}