「CF 1483E」Vabank(交互,构造)
题意:
你要猜出一个整数 \(M\)(\(0 \le M \le 10^{14}\))。一开始,你有 \(1\) 元钱。每次,你可以询问一个整数 \(X\),接下来:
- 若 \(X \le M\),你会获得 \(X\) 元钱;
- 若 \(X > M\),你会损失 \(X\) 元钱;
- 你能知道是 \(X \le M\) 还是 \(X > M\)。
任何时刻,你要保证你的存款非负。你要在 \(105\) 次询问内问出 \(M\)。
思路:
维护当前 \(M\) 所在的区间 \([L, R)\),每询问一次就对这个区间进行收缩。
当 \(L\) 足够大时,只要询问 \(L\) 就能白嫖 \(L\) 元。所以我们先要把 \(L, R\) 变为同一级别。只要依次询问 \(1, 2, 4, 8, \ldots\) 即可。
发现当 \(L, R\) 同级时,询问的 \(X\) 也与它们同级。因此只要有一定的本金,一次收益和一次损失可以看作抵消。
更好的解释是,一次收益和一次损失总共最多亏损 \(R - L\) 元,而随着询问次数增多 \(R - L\) 指数级减少,所以 \(\sum R - L\) 是 \(O(M)\) 的。
所以,我们设计 \(f_{i, j}\) 表示用 \(i\) 次询问,当前还能损失 \(j\) 次时能问出的最大区间长度。那么有转移:
- \(f_{i, j} = f_{i - 1, j - 1} + f_{i - 1, j + 1} \ (j > 0)\)
- \(f_{i, 0} = f_{i - 1, 1}\)
按照转移式进行询问即可。
代码:
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = (a); i <= int(b); i++)
#define per(i, a, b) for (int i = (a); i >= int(b); i--)
using namespace std;
typedef long long ll;
int T;
bool ask(ll x) {
printf("? %lld\n", x);
fflush(stdout);
char s[10];
scanf("%s", s);
return s[0] == 'L' ? 1 : 0;
}
void putans(ll m) {
printf("! %lld\n", m);
fflush(stdout);
}
ll dp[70][70];
void init(int n) {
rep(i, 0, n) {
dp[0][i] = 1;
}
rep(i, 1, n) {
dp[i][0] = dp[i - 1][1];
rep(j, 1, n - i) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
}
}
}
ll get(int a, int b, ll l, ll r) {
if (r - l == 1) {
return l;
}
if (b == 0) {
ask(l);
return get(a - 1, 1, l, r);
}
ll c = min(dp[a - 1][b - 1], r - l - 1);
if (!ask(l + c)) {
return get(a - 1, b - 1, l, l + c);
} else {
return get(a - 1, b + 1, l + c, r);
}
}
void work() {
if (!ask(1)) {
return putans(0);
}
ll l = 0, r = 0;
rep(i, 1, 46) {
if (!ask(1ll << i)) {
l = 1ll << (i - 1);
r = 1ll << i;
break;
}
}
if (!l) {
l = 1ll << 46;
r = ll(1e14) + 1;
}
int k = 0;
rep(i, 0, 59) {
if (dp[i][1] >= r - l) {
k = i;
break;
}
}
rep(i, 1, 6) {
ask(l);
}
ll ans = get(k, 1, l, r);
putans(ans);
}
int main() {
init(60);
scanf("%d", &T);
while (T --> 0) {
work();
}
return 0;
}
评价:
思维难度:5/10
代码难度:3/10
评分:3/10