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

可以看到每一个点都可以通过某种方式划分为几个不同的集合(树的同一层中每个点的划分方式不一定相同)。我们要做的就是尽可能均匀地划分父节点这个集合。这样分解得比较快。
那么看看阶乘有啥性质吧,看不出来,我会高精度!直接把所有阶乘处理出来得了。然后一开始我们的集合是 \([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;
}
本文来自博客园,作者:GE9x,转载请注明原文链接:https://www.cnblogs.com/GE9X/p/19848949

浙公网安备 33010602011771号