题意:交互题。现在有个隐藏的数 \(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;}elsenwc -= 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;elserp = pos;}cout << "! " << lp << endl;
}
signed main() {int T; cin >> T;while(T--)solve();return 0;
}