猜数(IO交互版) 的题解
记住只在没有思路时使用题解,不要从它复制粘贴代码。请尊重题目和题解的作者。
在解题之前提交题解的代码会导致封禁。
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求在未知整数 上,用不超过
次「猜一个数、得到它与答案大小关系」的询问确定
。核心解法是二分查找:每次询问当前候选区间的中点,按反馈把区间折半,
次以内必然命中。
分析
核心观察
反馈与猜测值相对答案的大小是一一对应的:返回 说明猜的数小于答案,返回
说明猜的数大于答案,返回
说明命中。因此每次询问都能排除掉「不可能包含答案」的那一半区间——这正是二分查找所需的全部信息。
思路
设当前候选区间为 ,取中点
并询问
:
- 返回
:命中,答案就是
,立刻结束程序;
- 返回
:
小于答案,答案落在
,令
;
- 返回
:
大于答案,答案落在
,令
。
每次询问后区间长度至少减半( 从
变为至多
)。初始区间长度为
,而
所以最多 次询问就能把区间缩到单个整数,远小于题面允许的
次。当
时区间为空,说明之前的判断出现了矛盾,正常情况下不会发生。
具体示例
以 为例,二分过程的前几步:
| 区间 |
反馈 | 区间变化 | |
|---|---|---|---|
每次区间长度减半,约 次后区间收缩到
,此时询问返回
,程序结束。
算法步骤
- 令
lo为,
hi为。
- 当
lo <= hi时重复:计算mid = lo + (hi - lo) / 2,输出mid并换行,随后刷新缓冲区。 - 从标准输入读入反馈
res。 - 若
res为,直接结束程序。
- 若
res为,令
lo = mid + 1;否则(res为)令
hi = mid - 1,回到第 2 步。
复杂度分析
- 时间:询问次数为
,其中
,即最多约
次;每次询问只做常数次算术与输入输出。
- 空间:
。只需要
lo、hi、mid、res四个变量。
实现注意事项
- 每次输出后必须刷新缓冲区。C++ 用
std::endl(换行并刷新)或std::cout << std::flush,Python 用print(mid, flush=True)。少写刷新会让评测程序收不到询问,双方互相等待,最终表现为超时。 - 返回
表示猜中,此时应立即结束程序,不要再输出任何内容;继续询问会因为评测程序已经退出而读到文件结束符。
的含义是「猜的数小于答案」,也就是答案在右半边,更新的是
lo;把方向写反会让区间朝错误方向收缩。- 询问的整数必须落在
内,越界询问会被判定为答案错误。
- 二分结束时
lo > hi,说明程序逻辑有误;正常情况下一定会在某次询问返回时提前结束。
源代码
#include <iostream>
int main() {
long long lo = 1, hi = 1000000000LL;
while (lo <= hi) {
long long mid = lo + (hi - lo) / 2;
// std::endl 输出换行并刷新缓冲区,缺少刷新会导致交互死锁
std::cout << mid << std::endl;
int res;
if (!(std::cin >> res)) {
break;
}
if (res == 0) {
return 0;
}
if (res == -1) {
lo = mid + 1; // mid 小于答案,答案在右半区间
} else {
hi = mid - 1; // mid 大于答案,答案在左半区间
}
}
return 0;
}
import sys
def main():
lo, hi = 1, 10 ** 9
while lo <= hi:
mid = lo + (hi - lo) // 2
# flush=True 输出换行并刷新缓冲区,缺少刷新会导致交互死锁
print(mid, flush=True)
res = int(sys.stdin.readline())
if res == 0:
return
if res == -1:
lo = mid + 1 # mid 小于答案,答案在右半区间
else:
hi = mid - 1 # mid 大于答案,答案在左半区间
main()
评论