Codeforces1990 E1/E2. Catch the Mole(Easy Version/Hard Version) (交互题,思维,均值不等式复杂度计算)

题意:

在这里插入图片描述
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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值