题目
题目描述
这是一道 交互题。
有一棵以 1 1 1 为根的树;可以通过询问得知 x , y x,y x,y 的树上距离,请还原这棵树(输出每个点的父节点)。
询问次数应当控制在 n log n n\log n nlogn 内(注:原题中是绝对数值,但翻译过来就是这个)。
数据范围与提示
原题:
n
⩽
3000
n\leqslant 3000
n⩽3000 。可行范围:
n
⩽
1
0
5
n\leqslant 10^5
n⩽105 。
思路
脑洞其实挺大。最重要的是 打开思路(尽管这也只是一句口号)。
最可悲的是,哪怕我做过原题;哪怕这道题有两种思路;哪怕这两种思路都并不离奇;我还是不会做。这就是弱者的悲哀。弱者抱团取暖,因为他们心底都只剩下漆黑的寒冷。
第一种想法是,根据 d i s ( x , y ) = d e p ( x ) + d e p ( y ) − 2 d e p ( l c a ) dis(x,y)=dep(x)+dep(y)-2dep(lca) dis(x,y)=dep(x)+dep(y)−2dep(lca),如果提前询问出所有点的深度,询问等价于询问 l c a lca lca 的深度。又根据树是二叉的,就可以知道 y y y 在 l c a lca lca 不含 x x x 的子树中。一直向下,就可以知道 y y y 的确切位置(假如 x x x 是已知的)。
首先要明确的是:要按照深度逐层处理。因为我们要通过 d e p ( l c a ) dep(lca) dep(lca) 就知道 l c a lca lca,肯定说明 x x x 的祖先都要被找到了;同时还要保证 y y y 的祖先都被找到了,否则无法唯一确定。我连这个都没想,直接就开始考虑所谓 “正解”,真是可笑!
此时,我们可以考察一下询问次数。很容易发现,假如 y y y 确定在 r r r 子树内,那么每次询问就是走到链 ⟨ r , x ⟩ \langle r,x\rangle ⟨r,x⟩ 的一条子链。也就是一个链划分的问题。可以发现,最坏询问次数就是 “虚边” 的数量;所以重链剖分之后,可以做到 n log n n\log n nlogn 次询问。
然而,在不知道原树之前,我们无法得知重链剖分结果。吾卒凝滞于此!何必用原树的重链剖分结果?用当前的重链剖分结果就行了啊!重链剖分并不是均摊的结果,而是严格的每次 O ( log n ) \mathcal O(\log n) O(logn),所以不需要全局性的考虑!
于是,只需要动态维护重链剖分。一种 简单的偷懒 方法是,每插入
O
(
n
)
\mathcal O(\sqrt n)
O(n) 个点,暴力重构一次重链剖分;在不重构的时候,“随机链剖分”。不难发现,如果原树是超级左偏树(一条左儿子链,每个点接一个右儿子),在两次重构之间,有
n
2
\sqrt{n}\over 2
2n 条链上边,都是随机剖分的,所以期望是
n
4
\sqrt{n}\over 4
4n 条轻边。这还是略大了些;所以要勤于重构,平衡一下。
老老实实地做,可能就需要引入替罪羊:若重儿子子树大小的占比不小于 α \alpha α,认为重链仍平衡。那么重链上就维护 s i z e ( s o n ) − α ⋅ s i z e ( x ) size(son)-\alpha\cdot size(x) size(son)−α⋅size(x) 即可;新加叶子时,相当于区间加 ( 1 − α ) (1-\alpha) (1−α),轻边上端点只减 α \alpha α 。维护 min \min min 值,发现 min < 0 \min<0 min<0 时则重构即可。
第一个思路就讲完了。第二个思路是,树是二叉的,说明一个点的度数至多是 3 3 3;那么,询问 d i s ( x , y ) dis(x,y) dis(x,y) 后,再询问 d i s ( u , y ) ( ⟨ x , u ⟩ ∈ E ) dis(u,y)\;(\langle x,u\rangle\in E) dis(u,y)(⟨x,u⟩∈E),就可以确定 y y y 在以 x x x 为根时的哪个子树内。显然我们应该找子树重心。
从 1 1 1 开始拓展连通块,还是应该按照深度处理,问题变为动态点分树。又是替罪羊登场的时间了……
第三个思路:前面说 “重链剖分不基于均摊”,所以肯定有基于均摊的方法。那就是 LCT \textit{LCT} LCT 呗。访问轻边的数量是均摊 O ( log n ) \mathcal O(\log n) O(logn) 的,完了。
代码
这里给出
O
(
n
)
\mathcal O(\sqrt n)
O(n) 次重建的做法。其他的太难打了。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <random>
#include <vector>
using namespace std;
typedef long long llong;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline int visit(int x,int y){
printf("? %d %d\n",x,y);
fflush(stdout); return readint();
}
int get_sqrt(int n){
int x = 1; // I hate binary search
rep(i,1,20) x = (x+(n/x))>>1;
return x; // Newton Iteration
}
const int MAXN = 100005;
int ch[MAXN][2], siz[MAXN];
void scan(int x){
drep(j,siz[x]=1,0) if(ch[x][j])
scan(ch[x][j]), siz[x] += siz[ch[x][j]];
}
int top[MAXN], ass[MAXN];
vector<int> chain[MAXN];
void build(int x,int bel){
if(!x) return ; // nothing remains
chain[top[x] = bel].push_back(x);
if(!ch[x][0] && !ch[x][1]) return void(ass[x] = x);
int d = (siz[ch[x][1]] > siz[ch[x][0]]);
build(ch[x][d],bel), ass[x] = ass[ch[x][d]];
build(ch[x][d^1],ch[x][d^1]); // light son
}
int dep[MAXN]; vector<int> buc[MAXN];
void findtree(int n,int *p){
buc[0].push_back(1); // for completeness
rep(i,2,n){
dep[i] = visit(1,i);
buc[dep[i]].push_back(i);
}
for(int i=0,len=int(buc[1].size()); i!=len; ++i)
p[ch[1][i] = buc[1][i]] = 1;
scan(1), build(1,1); // stupid enough
const int SQRTN = get_sqrt(n);
for(int d=2,cnt=0; d!=n; ++d){
for(const int &x : buc[d]){
int now = 1; // top of chain
for(int bot=ass[now]; true; bot=ass[now]){
int lca = dep[bot]+dep[x]-visit(bot,x);
if((lca >>= 1) == dep[bot]) break;
now = chain[now][lca-dep[now]];
int v = (top[ch[now][1]] == top[now]);
now = ch[now][v^1]; // in light son's subtree
}
p[x] = ass[now]; // just record this
if(ch[p[x]][0]) ch[p[x]][1] = x;
else ch[p[x]][0] = x; // link child
++ cnt; // how many operations done
}
for(const int &x : buc[d-1]){
if(!ch[x][0] && !ch[x][1]) continue;
static mt19937 rnd(114514);
static bernoulli_distribution _sy(0.5);
int v = ch[x][1] ? _sy(rnd) : 0;
ass[top[x]] = ch[x][v], top[ch[x][v]] = top[x];
chain[top[x]].push_back(ch[x][v]); // new leaf
if(ch[x][v^1]){ // another become a single node
top[ch[x][v^1]] = ass[ch[x][v^1]] = ch[x][v^1];
chain[ch[x][v^1]].push_back(ch[x][v^1]);
}
}
if(cnt > SQRTN){
for(int _d=0; _d<=d; ++_d)
for(const int &_x : buc[_d])
chain[_x].clear(); // clear all
scan(1), build(1,1), cnt = 0;
}
}
}
int fa[MAXN];
int main(){
int n = readint();
findtree(n,fa); putchar('!');
rep(i,2,n) printf(" %d",fa[i]);
putchar('\n'); fflush(stdout);
return 0;
}