题面大意
警告:本题要及时清空缓存区。
你知道一棵树有
n
n
n 个节点,且你可以询问最多
⌈
n
2
⌉
\lceil \dfrac{n}{2} \rceil
⌈2n⌉ 次,格式为 ? x
,每次询问评测机给出
n
n
n 个整数,第
i
i
i 个数代表
i
i
i 节点与
x
x
x 节点之间的简单路径长度。
所有询问结束后,要求输出一个 !
,下面
n
−
1
n-1
n−1 行,每行
2
2
2 个正整数 u v
,表示
u
u
u 节点和
v
v
v 节点间有边。
解决
注意到我们不知道树根,所以我们可以把 1 1 1 节点当作树根,我们先对树根进行询问,得到 n n n 个数,第 i i i 个数即表示 i i i 节点的深度,这里树根的深度认为是 0 0 0。
题目中说到可以询问最多 ⌈ n 2 ⌉ \lceil \dfrac{n}{2} \rceil ⌈2n⌉ 次,想到了对与节点的深度分成两类,分为奇数和偶数,如果奇数深度的节点少我们就全部询问奇数深度的节点,反之同理。对于相邻的两个节点,他们的简单路径长度一定为 1 1 1。
例如样例 1 1 1,第一次询问询问树根 1 1 1,给出了各个节点的深度。作图来自 Graph Editor。
发现奇数深度的节点有
1
1
1 个,偶数深度的节点有
2
2
2 个(不包含树根,因为没有查询的必要),所以我们选择奇数深度的节点询问。询问
2
2
2 节点,给出四个数 1 0 1 1
,即表示
1
1
1 节点、
3
3
3 节点、
4
4
4 节点与
2
2
2 节点有连边,最后把这三个边输出即可。
正确性证明
这样做的正确性很容易证明,因为奇数深度的节点一定只会与偶数深度的节点相邻而不会与奇数深度的节点相邻,偶数深度的节点一定只会与奇数深度的节点相邻而不会与偶数深度的节点相邻。
给个例子。作图来自画图。
我们选择奇数深度的节点询问,对于每次询问,我们都会得到该节点与上下两层偶数深度的节点之间的连边,这里我们把边进行分层。如下图。
对于每一层深度为奇数的节点,询问后加的边即为两层连续的奇数深度的边和偶数深度的边。这样在询问完所有深度为奇数的节点后我们就可以加到所有的边了。选择奇数深度的节点询问同理。
代码
#include<bits/stdc++.h>
using namespace std;
int n,dep[2010],cntodd,cnt;
inline void ask(const int &pos)
{
printf("? %d\n",pos),fflush(stdout);
}
struct Edge
{
int from,to;
};
Edge edge[2010];
inline void add(const int &x,const int &y)
{
edge[++cnt].from=x,edge[cnt].to=y;
}
int main()
{
scanf("%d",&n),ask(1);
for(int i=1;i<=n;i++) scanf("%d",&dep[i]),cntodd+=(dep[i]&1);//奇数深度统计一下
if(cntodd*2<n)//奇数深度数量少,不大于一半
{
for(int i=2,dis;i<=n;i++)
if(dep[i]&1)
{
ask(i);
for(int j=1;j<=n;j++)
{
scanf("%d",&dis);
if(dis==1) add(i,j);
}
}
}else//相反,偶数深度数量少
{
for(int i=1;i<=n;i++) if(dep[i]==1) add(1,i);//因为问偶数深度,而树根 1 的儿子都是深度为 1,是奇数,所以先连一波边
for(int i=2,dis;i<=n;i++)//和上面几乎一样
if(!(dep[i]&1))
{
ask(i);
for(int j=1;j<=n;j++)
{
scanf("%d",&dis);
if(dis==1) add(i,j);
}
}
}
putchar('!'),putchar('\n');
for(int i=1;i<n;i++) printf("%d %d\n",edge[i].from,edge[i].to);
return 0;
}