题意:
n<=5000
E1解法:
三个重要结论:
1. 如果松鼠不在子树x中,那么子树x可以删除
2. 如果我们反复查询某个松鼠不在的叶子节点,松鼠会一直往根节点走
3. 如果松鼠在子树x中,我们反复查询松鼠不在的叶子节点,可以把松鼠从子树赶出去.
做法:
预处理dep[i]和mdep[i], 其中mdep[i]表示点i子树中的最大深度.
我们可以这样找到松鼠:
1. 找到某个点nd,该点满足mdep[i]-dep[i]+1 == t
满足这个条件意味着子树nd的子链最大长度为t
2. 如果能够找到满足条件的点nd,我们判断松鼠是否在子树nd中:
a. 如果不在子树nd中,则将子树nd删除。
由于最大子链长度为t,则子树nd至少有t个节点,
因此这样的操作次数一定不超过n/t次.
b. 如果松鼠在子树nd中,由于子树深度为t,我们通过最多t次操作将松鼠赶出子树nd.
具体做法是任选一个叶子反复查询,每次查询完查询松鼠是否还在子树nd中。
如果某一时刻松鼠不在子树nd中了,则松鼠一定在pre[pre[nd]]位置。
3. 如果没有满足条件的点nd,那么说明当前树的最大深度不超过t,因此我们进行t次驱赶操作就能将松鼠赶到节点1.
复杂度分析:
对于操作2.a,我们最多操作n/t次
对于操作2.b,我们最多操作2t次
对于操作3,该操作只在2.b未生效时出现,此时我们最多操作t次.
因此最坏情况下,我们的操作次数大概为n/t+2t次.
由均值不等式可知当n/t==2t时,n/t+2t达到最小值,此时t=sqrt(n/2).
由于N最大值为5000, 因此t=sqrt(5000/2)=sqrt(2500)=50.
操作次数最大值为2*sqrt(2t*n/t)=2*sqrt(2N)=200.
满足题目操作次数<=300的条件.
E2 解法:
复杂度瓶颈在于操作2.b,每次查询叶子之后都要查询hd,判断松鼠是否走出了子树hd。
其实直接进行t次驱赶操作后,松鼠一定在[1,hd]这条链上,我们对链进行二分即可.
复杂度就从2t优化为了t+log(n)
总操作次数为n/t+t+log(n)
当n/t==t时,即t=sqrt(N)时最小,总操作次数为2*sqrt(N)+log(N),
大概=100*sqrt(2)+log(N).
100*sqrt(2)大约144
log(N)<=13
总操作次数<=160, 能够满足题目要求.
E1 Code:
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define int long long
#define PI pair<int, int>
const int maxm=2e6+5;
// const int mod=998244353;
const int mod=1e9+7;
int n;
vector<int>g[maxm];
int del[maxm];
int dep[maxm];
// mdep[i]表示子树i中的最大深度
int mdep[maxm];
int pre[maxm];
int ask(int x){
cout<<"? "<<x<<endl;
int ans;cin>>ans;return ans;
}
void out(int x){
cout<<"! "<<x<<endl;
}
// 初始化pre[]
void init(int x,int fa){
pre[x]=fa;
for(int v:g[x]){
if(v==fa)continue;
init(v,x);
}
}
// 计算dep[]和mdep[]
void dfs(int x) {
mdep[x]=dep[x];
for(int v:g[x]){
if(v==pre[x]||del[v])continue;
dep[v]=dep[x]+1;
dfs(v);
mdep[x]=max(mdep[x],mdep[v]);
}
}
// 删除子树x
void del_x(int x){
del[x]=1;
for(int v:g[x]){
if(v==pre[x])continue;
del_x(v);
}
};
// 获取任意未被删除的叶子
int get_leaf(){
int leaf=-1;
for(int i=1;i<=n;i++){
if(del[i])continue;
// 深度最大的是叶子节点
if(leaf==-1||dep[i]>dep[leaf]){
leaf=i;
}
}
assert(leaf!=-1);
return leaf;
}
void solve() {
cin>>n;
for(int i=1;i<=n;i++){
g[i].clear();
del[i]=0;
dep[i]=0;
mdep[i]=0;
pre[i]=0;
}
for(int i=1;i<n;i++){
int l,r;cin>>l>>r;
g[l].push_back(r);
g[r].push_back(l);
}
init(1,0);
constexpr int N=5000;
int sq=sqrt(N/2);
while(true){
dep[1]=1;
dfs(1);
// 找存在节点数为sq子链的节点nd
int nd=-1;
assert(dep[1]==1);
for(int i=1;i<=n;i++){
if(del[i])continue;
int cnt=mdep[i]-dep[i]+1;
// 最长子链节点个数为sq
if(cnt==sq){
nd=i;break;
}
}
if(nd==-1){
// 没有长度为sq子链的节点了
// 那么所有剩余节点的深度都<=sq
// 此时访问叶子最多sq次后根节点1就是答案
assert(mdep[1]<sq);
int leaf=get_leaf();
for(int i=1;i<=sq;i++){
int q=ask(leaf);
if(q==1){
out(leaf);
return ;
}
}
out(1);
return ;
}
// 有长度为sq子链的节点
int q=ask(nd);
if(q==0){
// 不在子树nd中
// 删除子树nd
// 至少删除sq个节点, 因此删除操作最多n/sq次
assert(nd!=1);
del_x(nd);
} else {
// 在子树nd中
// 那么访问叶子最多sq次后,fa[nd]就是答案
// 每次访问后都要检查松鼠是否还在子树nd中, 最多检查sq次
// 因此最多操作2*sq次
int leaf=get_leaf();
for(int i=1;i<=sq;i++){
int q=ask(leaf);
if(q==1){
out(leaf);
return ;
}
q=ask(nd);
if(q==0){
// 此时会跳到pre[pre[nd]]上
int ffa=pre[nd];
if(ffa!=1)ffa=pre[ffa];
out(ffa);
return ;
}
}
// 如果sq次还在子树中,那么一定是在子树1中.
assert(nd==1);
out(1);
return ;
}
}
assert(false);
}
signed main() {
#define MULTI_CASE
ios::sync_with_stdio(0);
cin.tie(0);
#ifndef ONLINE_JUDGE
freopen("../in.txt", "r", stdin);
freopen("../out.txt", "w", stdout);
#endif
#ifdef MULTI_CASE
int T;
cin >> T;
while (T--)
#endif
solve();
return 0;
}
E2 Code:
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define int long long
#define PI pair<int, int>
const int maxm=2e6+5;
// const int mod=998244353;
const int mod=1e9+7;
int n;
vector<int>g[maxm];
int del[maxm];
int dep[maxm];
// mdep[i]表示子树i中的最大深度
int mdep[maxm];
int pre[maxm];
int ask(int x){
cout<<"? "<<x<<endl;
int ans;cin>>ans;return ans;
}
void out(int x){
cout<<"! "<<x<<endl;
}
// 初始化pre[]
void init(int x,int fa){
pre[x]=fa;
for(int v:g[x]){
if(v==fa)continue;
init(v,x);
}
}
// 计算dep[]和mdep[]
void dfs(int x) {
mdep[x]=dep[x];
for(int v:g[x]){
if(v==pre[x]||del[v])continue;
dep[v]=dep[x]+1;
dfs(v);
mdep[x]=max(mdep[x],mdep[v]);
}
}
// 删除子树x
void del_x(int x){
del[x]=1;
for(int v:g[x]){
if(v==pre[x])continue;
del_x(v);
}
};
// 获取任意未被删除的叶子
int get_leaf(){
int leaf=-1;
for(int i=1;i<=n;i++){
if(del[i])continue;
// 深度最大的是叶子节点
if(leaf==-1||dep[i]>dep[leaf]){
leaf=i;
}
}
assert(leaf!=-1);
return leaf;
}
void solve() {
cin>>n;
for(int i=1;i<=n;i++){
g[i].clear();
del[i]=0;
dep[i]=0;
mdep[i]=0;
pre[i]=0;
}
for(int i=1;i<n;i++){
int l,r;cin>>l>>r;
g[l].push_back(r);
g[r].push_back(l);
}
init(1,0);
constexpr int N=5000;
int sq=sqrt(N);
while(true){
dep[1]=1;
dfs(1);
// 找存在节点数为sq子链的节点nd
int nd=-1;
assert(dep[1]==1);
for(int i=1;i<=n;i++){
if(del[i])continue;
int cnt=mdep[i]-dep[i]+1;
// 最长子链节点个数为sq
if(cnt==sq){
nd=i;break;
}
}
if(nd==-1){
// 没有长度为sq子链的节点了
// 那么所有剩余节点的深度都<=sq
// 此时访问叶子最多sq次后根节点1就是答案
assert(mdep[1]<sq);
int leaf=get_leaf();
for(int i=1;i<=sq;i++){
int q=ask(leaf);
if(q==1){
out(leaf);
return ;
}
}
out(1);
return ;
}
// 有长度为sq子链的节点
int q=ask(nd);
if(q==0){
// 不在子树nd中
// 删除子树nd
// 至少删除sq个节点, 因此删除操作最多n/sq次
assert(nd!=1);
del_x(nd);
} else {
// 在子树nd中
// 那么访问叶子sq次后,松鼠一定会在[1,hd]范围内
// 对[1,nd]这条链二分,找松鼠的位置即可.
// 最多操作sq+log次
int leaf=get_leaf();
for(int i=1;i<=sq;i++){
int q=ask(leaf);
if(q==1){
out(leaf);
return ;
}
}
// 松鼠一定在[1,hd]这条链上.
vector<int>link;
while(nd!=0){
link.push_back(nd);
nd=pre[nd];
}
reverse(link.begin(),link.end());
assert(link.size()>=1);
// 二分找松鼠的位置
int l=0,r=link.size()-1;
while(l<r){
int mid=(l+r)/2;
// 查询l是没意义的, 因为l是子树的顶点
if(mid==l)mid++;
int nd=link[mid];
int q=ask(nd);
if(q==0){
// 不在[mid,r]中
// [mid,r]可以删除
r=mid-1;
// 因为松鼠会往上爬,所以r--,l--
if(r!=0)r--;
if(l!=0)l--;
} else {
// 在[mid,r]中
l=mid;
}
}
assert(l==r);
out(link[l]);
return ;
}
}
assert(false);
}
signed main() {
#define MULTI_CASE
ios::sync_with_stdio(0);
cin.tie(0);
#ifndef ONLINE_JUDGE
freopen("../in.txt", "r", stdin);
freopen("../out.txt", "w", stdout);
#endif
#ifdef MULTI_CASE
int T;
cin >> T;
while (T--)
#endif
solve();
return 0;
}