题意:
你要猜出一个整数 (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
", x);
fflush(stdout);
char s[10];
scanf("%s", s);
return s[0] == 'L' ? 1 : 0;
}
void putans(ll m) {
printf("! %lld
", 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