题解:CF1483E Vabank
题意:交互题。现在有个隐藏的数 \(M\),你可以询问 \(X\) 和 \(M\) 的大小关系,如果 \(X\le M\) 则你会增加 \(X\) 块钱,如果 \(X>M\) 你需要支付 \(X\) 块钱,如果你付不起就挂了。初始有 \(1\) 块钱,要找出 \(M\) 是多少,最多 \(105\) 次询问。\(M\le 10^{14}\)。
做法:
首先我们先不管支付的事情,那么就是直接二分,有付钱这个事情就不是很好做,直接二分的话我们需要补次钱。我们一开始肯定还是直接倍增,这样刚好是在我有钱的前提下尽量扩大范围去找。同时这样可以更快地缩小范围,方便我们去补钱这一部分最多需要用掉 \(47\) 次操作。
然后我们这个时候就可以找出来答案应该在 \([2^x,2^{x+1})\) 这个范围内,这个时候我们再去进行二分。
但是这里有个问题,正常的二分向左向右是没有什么去别的,这里向左是有负的代价而向右是有正的贡献的,并不是同等地位的,所以我们其实应该考虑适当地向左偏移一点,这样去平衡一下两侧的代价。假设我取在整条线段的 \(\frac{1}{p}\) 的位置,那么向左一次,我们就需要补 \([1,2]\) 次钱,\(1\) 次钱居多,最少的步数就是 \(2\log_p(r-l)\) 次,这里 \([l,r)\) 是确定的答案区间。那么因为剩下 \(58\) 次操作,就大概要求 \(\frac 1 p \ge 0.3\) 左右,实则并没有这么严因为这本身就是个比较松的做法。
然后我们考虑怎么取这个位置,那我这里肯定是和钱有关的,我的钱越多肯定是更不怕挂掉的,当我怎么用钱都不会挂的时候就全部二分就行;当我没钱的时候,我肯定是取在 \(\frac{1}p\)。因为我不会分析,所以我认为放置位置关于钱数是线性的。粗略地估计最大钱数,那就是每次都挂掉我还能继续二分,大概是 \(l\log_2(r-l)+r-l\),就是每轮先认为要 \(l\) 块钱再算一下剩下的贡献,取 \(\frac 1 p = 0.3\),实测可以在 \(103\) 次内解决。
(尝试卡了几下但是最后还是在 \(103\) 次止步了。)
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
int nwc;
bool query(int x) {
cout << "?" << " " << x << endl;
string s; cin >> s;
if(s == "Lucky!") {
nwc += x;
return 1;
}
else
nwc -= x;
return 0;
}
void solve() {
nwc = 1;
int bs = 1;
while(bs <= (int)(1e14)) {
if(!query(bs))
break;
bs <<= 1;
}
int lp = (bs >> 1), rp = min((int)(1e14), bs - 1) + 1;
while(lp + 1 < rp) {
int mid = lp + rp >> 1, len = rp - lp, pos;
double rat = log2(len) * lp + 2 * (mid - lp);
if(nwc >= rat)
pos = mid;
else {
double t = 0.3 + 0.2 * nwc / rat;
pos = lp + (rp - lp) * t;
}
while(nwc < pos)
query(lp);
if(query(pos))
lp = pos;
else
rp = pos;
}
cout << "! " << lp << endl;
}
signed main() {
int T; cin >> T;
while(T--)
solve();
return 0;
}

浙公网安备 33010602011771号