交互题.
给定一棵完全二叉树,树高为 h , n = 2 h − 1 h,n=2^h-1 h,n=2h−1.
现在你不知道每个点的位置,但是你可以进行 n + 420 n+420 n+420次询问.
询问格式为? x y z
,返回当 z z z为根时 x , y x,y x,y的 L C A LCA LCA.
现在让你回答根的标号.
h ∈ [ 3 , 18 ] h\in [3,18] h∈[3,18]
参考 C F CF CF官方题解.
这道题让我重新认识了
L
C
A
LCA
LCA.
以
z
z
z为根,
L
C
A
(
x
,
y
)
LCA(x,y)
LCA(x,y)是到
x
,
y
,
z
x,y,z
x,y,z距离和最小的点.(证明显然…)
所以询问
x
,
y
,
z
x,y,z
x,y,z的顺序是无关紧要的.
我们考虑
m
i
d
=
a
s
k
(
x
,
y
,
z
)
∉
{
x
,
y
,
z
}
mid=ask(x,y,z)\notin\{x,y,z\}
mid=ask(x,y,z)∈/{x,y,z}的情况.
我们考虑每个点作为
m
i
d
mid
mid能被多少个三元组得到.
显然设
s
1
,
s
2
,
s
3
s1,s2,s3
s1,s2,s3为
m
i
d
mid
mid三个子树的大小.(不足时补0).
那么
c
o
u
n
t
(
m
i
d
)
=
s
1
∗
s
2
∗
s
3
count(mid)=s1*s2*s3
count(mid)=s1∗s2∗s3.
显然根和叶子的
c
o
u
n
t
=
0
count=0
count=0,且容易发现深度相同
c
o
u
n
t
count
count相等.
而深度为
i
i
i的点(
i
≠
1
,
i
≠
h
)
i\ne 1,i\ne h)
i=1,i=h)的
c
o
u
n
t
=
(
2
h
−
i
−
1
)
2
(
2
h
−
2
h
−
i
+
1
)
count=(2^{h-i}-1)^2(2^h-2^{h-i+1})
count=(2h−i−1)2(2h−2h−i+1).
显然和为定值,积在尽量相等的时候取最大,所以根的儿子处取到最大值.
所以我们可以随机
420
420
420次,然后统计
a
s
k
(
x
,
y
,
z
)
ask(x,y,z)
ask(x,y,z)的出现次数.
出现次数最大的俩个为根的两个儿子.
然后再
O
(
n
)
O(n)
O(n)枚举根即可.
int h, n, cnt[T], p[T];
V<int> e[T];
#define pb push_back
void ins(int x,int y) {e[x].pb(y); e[y].pb(x);}
#define op false
int fa[T],dep[T],c;
void dfs(int x) {
c++;
for(int y:e[x]) if(fa[x] ^ y)
fa[y]=x,dep[y]=dep[x]+1,dfs(y);
}
int ask(int x,int y,int z) {
if(x == y) return x;
if(x == z) return z;
if(y == z) return z;
if(op) {
fa[z]=dep[z]=0; c=0;
dfs(z);
assert(c == n);
while(x^y) {
if(dep[x]<dep[y]) swap(x,y);
x=fa[x];
}
return x;
}
printf("? "); pr1(x); pr1(y); pr2(z);
fflush(stdout); qr(x);
assert(x>0);
return x;
}
mt19937 rnd(time(0));
int random(int x) {return rnd()%x+1;}
bool cmp(int x,int y) {return cnt[x]>cnt[y];}
void solve() {
qr(h); n=(1<<h)-1;
int rt=-1;
if(op) {
FOR(i,n) {
int x; qr(x);
if(x) ins(x,i);
else rt=i;
}
}
FOR(i,420) {
int x=random(n),y=random(n),z=random(n),w;
if(x == y || y == z || x == z) {i--; continue;}
w=ask(x,y,z);
if(w ^ x && w ^ y && w ^ z) cnt[w]++;
}
iota(p+1,p+n+1,1);
sort(p+1,p+n+1,cmp);
int x=p[1],y=p[2];
FOR(i,n) {
int z=ask(x,y,i);
if(z ^ x && z ^ y) {
printf("! %d\n",i);
if(op)
assert(i == rt);
return ;
}
}
}
int main() {
if(op) freopen("a.in","r",stdin);
int T = 1;
// qr(T);
while (T--)
solve();
return 0;
}