[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;
}
posted @ 2019-03-21 16:12  skylee03  阅读(207)  评论(0编辑  收藏  举报