【CF1370F2】The Hidden Pair (Hard Version)
题目
题目链接:https://codeforces.com/problemset/problem/1370/F2
这是一道交互题。困难版与简单版唯一的区别是交互次数限制。
本题有多组数据。
已知一棵 \(n\) 个顶点的树(边集已知),其中有两个不同的顶点被暗中做了标记。你现在需要通过若干次询问,猜出两个被标记顶点的编号。
一次询问的格式为 ? c x_1 x_2 ... x_c
,即代表你向交互库请求关于 \(x_1,x_2,\cdots,x_c\) 这 \(c\) 个点的信息。
对于一次询问,交互库的返回格式为 x d
,表示在询问的集合中,到两个被标记点的距离之和最小的点是 \(x\),这个最小值为 \(d\)。如果有多个最小值点,\(x\) 的值可能是其中任意一个。
如果已经知晓答案,请用 ! x y
的格式来输出你的答案,任意顺序均可。在这之后,你会收到一个字符串 Correct
或者 Incorrect
,代表你的猜测是否正确。如果收到了 Incorrect
,请立即终止程序,否则请继续处理下一组数据。
对于每组数据,请你在不超过 \(11\) 次询问之内给出答案。(Easy Version 是 \(14\) 次)
\(1\le t\le 10, 2\le n\le 1000\)。
思路
首先询问一遍所的点,可以得到两个标记点的距离 \(dis\),以及位于它们路径上的一个点。
然后以得到的这个点开始 dfs,用 vector 记录每一个点的深度。
然后二分深度,每次询问这个深度上的所有点。如果得到的距离是 \(dis\),说明较深的那一个点一定深度不小于当前二分的深度。
可以在 \(O(\log n)\leq 10\) 次询问找到其中一个标记点。以这个标记点为根 dfs,然后询问所有深度为 \(dis\) 的点,得到的就是另一个标记点了。
操作次数为 \(1+10+1=12\),可以过 Easy Version。
我们发现,因为第一次二分的是较深的点,它的深度一定不小于 \(\lceil\frac{dis}{2}\rceil\),这样我们二分的区间就变成了 \([\lceil\frac{dis}{2}\rceil,\min(dis,maxdep)]\)。显然这个的上界是 \(\frac{n}{2}\leq 500\),那么此时的操作次数为 \(1+\log \frac{n}{2}+1\geq 1+9+1=11\)。可以通过 Hard Version。
时间复杂度 \(O(Qn\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,Q,rt,dis,res,maxd,tot,head[N];
char ch[20];
vector<int> node[N];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void prework()
{
for (int i=0;i<=maxd;i++) node[i].clear();
memset(head,-1,sizeof(head));
tot=maxd=0;
}
void dfs(int x,int fa,int dep)
{
maxd=max(maxd,dep);
node[dep].push_back(x);
for (int i=head[x];~i;i=e[i].next)
if (e[i].to!=fa) dfs(e[i].to,x,dep+1);
}
int main()
{
scanf("%d",&Q);
while (Q--)
{
prework();
scanf("%d",&n);
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
// -----------------------------------------------------------
printf("? %d",n);
for (int i=1;i<=n;i++)
printf(" %d",i);
printf("\n");
fflush(stdout);
// -----------------------------------------------------------
scanf("%d%d",&rt,&dis);
dfs(rt,0,0);
int l=dis/2,r=min(maxd,dis),mid,x,y;
while (l<=r)
{
mid=(l+r)>>1;
// -----------------------------------------------------------
printf("? %d",node[mid].size());
for (int i=0;i<node[mid].size();i++)
printf(" %d",node[mid][i]);
printf("\n");
fflush(stdout);
// -----------------------------------------------------------
scanf("%d%d",&x,&y);
if (y==dis) l=mid+1,res=x;
else r=mid-1;
}
for (int i=0;i<=maxd;i++) node[i].clear();
dfs(res,0,0);
// -----------------------------------------------------------
printf("? %d",node[dis].size());
for (int i=0;i<node[dis].size();i++)
printf(" %d",node[dis][i]);
printf("\n");
fflush(stdout);
// -----------------------------------------------------------
scanf("%d%d",&x,&y);
printf("! %d %d\n",x,res);
fflush(stdout);
scanf("%s",ch);
}
return 0;
}