P7597
Solution
首先,我们可以用
考虑我们要确定这个点所有儿子的新的子孙。一个 Trival 的想法是全部用操作
考虑事实上新的子孙的情况是原来子孙情况的一个划分。所以我们确定了很多个儿子的子孙情况,而剩下一个就可以不用查询就直接得到结果。也就是我们能省掉很多的询问。
那显然是有限省掉重儿子啊。但是我们比不知道子树的大小,输。
那就交给概率,随便在这么多儿子里面随一个点。如果目前整个子树的大小为
于是在最劣情况下,你的期望询问次数有:
可以证明,
下面是我边想边写半个小时整出来的证明。
Warning:限于本人低劣的数学水平,下文可能存在错误。下文出现了大量的近似,因为我们只是研究一个渐进复杂度,没有必要算出具体数值(更何况这是基于随机数的期望的。)
使用数学归纳法。假设我们在计算
为啥用
\ln ?因为后面求导好算。
构造
我们发现,导函数的拐点为
考虑如果有一个
很容易发现,前者严格大于后者。也就是说,
因此我们就简化为:
根据主定理,
也许,是对的?
还是放一下代码吧。
#include<bits/stdc++.h>
#define ffor(i,a,b) for(int i=(a);i<=(b);i++)
#define roff(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int MAXN=5000+10;
int n,dep[MAXN],fa[MAXN],flg[MAXN];
vector<int> son[MAXN];
int query_dis(int u,int v) {
cout<<"? "<<1<<' '<<u<<' '<<v<<endl;
int ans; cin>>ans;
return ans;
}
vector<int> query_sons(int u) {
cout<<"? "<<2<<' '<<u<<endl;
vector<int> S; int cnt;
cin>>cnt;
ffor(i,1,cnt) {
int id; cin>>id;
if(id!=u) S.push_back(id);
}
return S;
}
mt19937 myrand(time(0));
void solve(int u) {
if(!son[u].size()) return ;
vector<int> dson;
for(auto id:son[u]) if(dep[id]==dep[u]+1) dson.push_back(id),fa[id]=u;
int rnd=son[u][myrand()%son[u].size()],hson=-1;
for(auto id:dson) if(rnd==id||(query_dis(rnd,id)==dep[rnd]-dep[id])) hson=id;
memset(flg,0,sizeof(flg));
for(auto id:dson) {
if(id==hson) continue ;
son[id]=query_sons(id);
for(auto Id:son[id]) flg[Id]=1;
}
for(auto id:son[u]) if(dep[id]!=dep[u]+1&&!flg[id]) son[hson].push_back(id);
son[u].clear();
for(auto id:dson) solve(id);
return ;
}
int main() {
cin>>n;
ffor(i,2,n) dep[i]=query_dis(1,i),son[1].push_back(i);
solve(1);
cout<<"! ";
ffor(i,2,n) cout<<fa[i]<<' ';
cout<<endl;
return 0;
}