题解: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;
}
posted @ 2025-10-16 19:29  LUlululu1616  阅读(9)  评论(0)    收藏  举报