Loading

CF1773I 猜阶乘 解题报告

这个就是非常非常典的交互问题了。一个基本的思想,就是通过查询不断缩小范围,直到符合条件的只有一个元素。注意到可能有 \(10^{10}\) 情况,区分 \(5000\) 完全够了。根据每一次查询结果的不同,会形成一棵树。长成下面这样:

Untitled

可以看到每一个点都可以通过某种方式划分为几个不同的集合(树的同一层中每个点的划分方式不一定相同)。我们要做的就是尽可能均匀地划分父节点这个集合。这样分解得比较快。

那么看看阶乘有啥性质吧,看不出来,我会高精度!直接把所有阶乘处理出来得了。然后一开始我们的集合是 \([1,5982]\) 的所有数。之后我们需要选择 \(10\) 个不同的位进行查询,每一次查询可能会有不同结果,会把当前的集合分解成 \(10\) 个不同的集合,这些集合加起来就是父节点的集合。

那么为了使这棵树分得更均匀,每一次我们遍历每一位数字,然后我们看出现频率最大的数字最小的是哪一位,使用这位查询就可以尽可能均匀地划分。然后直接提交,发现通过,那就说明这样划分,树的高度不超过 \(10\),时间复杂度 \(\mathcal{O}(能过)\)

顺带一提,这样子这棵树是唯一的。所以多次用到一个节点时,也可以直接记忆化。

#include <bits/stdc++.h>
using namespace std;
const int N = 6000, M = 20007;
int f[N][M], len[N], cnt[14];
vector<int> ans, tmp;
void pre(const int n = 5982){
	f[0][0] = 1, len[0] = 0;
	for(int i = 1; i <= n; i ++){
    len[i] = len[i - 1];
		for(int j = 0; j <= len[i - 1]; j ++){
			f[i][j] += f[i - 1][j] * i;
      f[i][j + 1] = f[i][j] / 10;
      f[i][j] %= 10;
		}
    while(f[i][len[i] + 1]){
      len[i] ++;
      f[i][len[i] + 1] = f[i][len[i]] / 10;
      f[i][len[i]] %= 10;
    }
	}
}
void solve(){
  ans.clear();
  for(int i = 1; i <= 5982; i ++) ans.push_back(i);
  for(int z = 1; z <= 10; z ++){
    int K= 2000;
    int mn = 1e9, mx, pos = -1;
    if(z == 1) pos = 1601;
    else{
      for(int i = 0; i <= K; i ++){
        mx = 0;
        for(int j = 0; j <= 9; j ++) cnt[j] = 0;
        for(int x: ans){
          cnt[f[x][i]] ++;
          mx = max(mx, cnt[f[x][i]]);
        }
        if(mn > mx) pos = i, mn = mx;
      }
    }
    cout << "? " << pos << endl;
    int res; cin >> res;
    tmp.clear();
    for(int x: ans){
      if(f[x][pos] == res) tmp.push_back(x);
    }
    ans = tmp;
    if(ans.size() == 1) break;
  }
  cout << "! " << ans[0] << endl;
  string rr; cin >> rr;
}
signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  pre();
  int t; cin >> t;
  while(t --) solve();
  return 0;
}
posted @ 2026-04-10 22:36  GE9x  阅读(27)  评论(0)    收藏  举报