【CF1129E】Legendary Tree
题目
题目链接:https://codeforces.com/problemset/problem/1129/E
这是一道交互题。
有一个 \(n\) 个节点的树,你需要通过不超过 \(11111\) 次询问得知树的形态。
询问方式为给出两个非空无交点集 \(S,T\) 和一个点 \(u\),可以得到满足 \(s \in S , t \in T\) 且路径 \((s,t)\) 经过 \(u\) 点的二元组 \((s,t)\) 的总数。
\(n\leq 500\)。
思路
树上交互题都是神仙吧。
设 \(1\) 为根,首先可以通过 \(n-1\) 次询问得到每一个点的子树大小(取 \(S=\{1\},T=\{2,3,\cdots n\},u=i\))。
将点按照子树大小排序,那么对于序列中的任意一个点,他的儿子都在他前面。
假设按照子树大小枚举到点 \(i\),维护一个集合 \(s\) 表示 \(i\) 子树比他小的点,且未选择父亲的点集。
然后可以询问一次得到点集中有多少个点的父亲是 \(i\),接下来分别找到每一个儿子,可以采用二分。
这样的话只会二分 \(n-1\) 次,时间复杂度 \(O(n^2\log n)\),次数上界为 \(2n+n\log n\leq 5500\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=510;
int n,siz[N],id[N],fa[N];
vector<int> s;
bool cmp(int x,int y)
{
return siz[x]<siz[y];
}
int print(int x,int l,int r)
{
cout<<"1\n1\n"<<r-l+1<<"\n";
for (int i=l;i<=r;i++) cout<<s[i]<<" ";
cout<<"\n"<<x<<"\n";
fflush(stdout);
scanf("%d",&x);
return x;
}
int main()
{
scanf("%d",&n);
for (int i=2;i<=n;i++) s.push_back(i);
siz[1]=n; id[1]=1;
for (int i=2;i<=n;i++)
siz[i]=print(i,0,n-2),id[i]=i;
s.clear();
sort(id+1,id+1+n,cmp);
for (int i=1;i<=n;i++)
{
if (s.size())
{
int cnt=print(id[i],0,s.size()-1),last=-1;
while (cnt--)
{
int l=last+1,r=s.size()-1,mid;
while (l<=r)
{
mid=(l+r)>>1;
if (print(id[i],l,mid)) r=mid-1;
else l=mid+1;
}
fa[s[r+1]]=id[i];
for (int i=r+2;i<s.size();i++)
s[i-1]=s[i];
s.pop_back();
}
}
s.push_back(id[i]);
}
cout<<"ANSWER\n";
for (int i=2;i<=n;i++)
cout<<i<<" "<<fa[i]<<"\n";
fflush(stdout);
return 0;
}