算法知识图谱总览 / 竞赛策略族 / 交互题策略

交互题策略

P2intermediatestandardG-E-8

What · 是什么

交互次数优化、二分交互、随机撒点、flush 时机、STDIO vs Grader 模式

交互题中程序与评测程序(interactor)一问一答完成任务,得分优劣常取决于查询/交互次数。关键要素:①输出后必须 flush(C++ fflush(stdout) / endl,Python sys.stdout.flush()),否则交互器收不到请求导致 Idleness Limit Exceeded(ILE);②STDIO 交互(CF/AtCoder/ICPC,问写标准输出、读标准输入)与 Grader 交互(IOI/APIO 的 CMS 平台,实现指定函数调用辅助函数);③次数优化:二分查找(判定一个单调属性)、随机撒点(链表/分布未知时)、三分/倍增;④读入所有数据再回答(多组数据时即使已知答案也要读完,否则 ILE);⑤静态查错能力要求高(无法方便对拍)。

Why · 为什么

直觉 · Intuition

交互器像真人裁判:你不发话它不回答。每问一次都有代价(题目给的询问上限),所以策略就是「用最少的确定性/概率性询问逼近答案」。标准套路:把交互建模成「对单调谓词的查询」→ 二分 O(log n) 次;无法直接二分 → 先用随机采样缩小范围再精确定位。flush 是「把话说出去」,不 flush 就像话憋在嗓子眼,交互器只能干等。

原理 · Principle

STDIO 交互基于管道:stdout 默认行缓冲/全缓冲,必须 flush 才能立即送达。Grader 交互通过函数返回值传输,无 flush 问题但语言受限(通常仅 C++/Pascal)。次数上限:CF 上超过询问限制判 Wrong Answer(错误说明里带原因),UVa 判 Protocol Limit Exceeded。经典策略:①确定型——二分查找(单调集合),O(log n);②概率型——先随机撒 k 个点收集分布信息,再沿贪心/边界前进(如 CF843B 随机撒 1000 点逼近链表目标值);③验证质数——一次问 19 个数(含平方)即可判定。

历史 · History

交互题自上世纪 IOI 已有,2019 年 NOI 系列连续出现(WC2019 I 君的商店、NOI2019 I 君的探险)标志其回归。OI Wiki 的 contest/interaction 页以 CF679A、CF843B、APIO2016 Gap、CF750F 等为例讲解 flush 时机、随机撒点、次数限制与特殊错误(ILE/PLE/PV),contest/problems 页区分 STDIO 与 Grader 两种交互方式并比较优劣。

How · 实现

伪代码

STDIO 二分示例(猜 1..n 中的数):
  lo,hi = 1,n
  while lo<hi:
    mid=(lo+hi)//2
    print('?', mid); sys.stdout.flush()
    ans = int(input())
    if ans == '>=': lo = mid+1  else: hi = mid
  print('!', lo); sys.stdout.flush()
随机撒点(CF843B式):
  若 n 小则全询问;否则随机问 1000 个点取最优起点,再沿 next 指针走

Python 实现

import sys


def guess_number(n):
    # 经典二分交互:交互器答案是 ans,我们问 '? x' 得到 x>=ans 与否
    # 这里用闭包模拟交互器便于自测
    secret = 42
    assert 1 <= secret <= n

    def ask(x):
        print(f"? {x}", flush=True)
        return x >= secret

    lo, hi = 1, n
    cnt = 0
    while lo < hi:
        mid = (lo + hi) // 2
        cnt += 1
        if ask(mid):
            hi = mid
        else:
            lo = mid + 1
    print(f"! {lo}", flush=True)
    assert lo == secret, lo
    return cnt


def guess_prime_style():
    # CF679A 风格:一次问 19 个数(质数 + 质数平方)判定 n 是否为质数
    import random
    n = random.choice([2, 3, 5, 97, 100, 143])
    candidates = [2, 3, 4, 5, 7, 9, 11, 13, 17, 19, 23, 25, 29, 31, 37, 41, 43, 47, 49]
    yes = 0
    for x in candidates:
        if n % x == 0:
            yes += 1
    is_prime = yes <= 1 and n >= 2
    # 自测:100=2^2*5^2 应判合数;143=11*13 合数;97 质数
    expected = n in (2, 3, 5, 97)
    assert is_prime == expected, (n, is_prime)
    return n, is_prime


def main():
    cnt = guess_number(1000)
    print(f"binary search used {cnt} queries, result correct")
    assert cnt <= 11  # log2(1000) ~ 10
    for _ in range(200):
        guess_prime_style()
    print("prime-style random tests OK")
    print("OK")


if __name__ == "__main__":
    main()
PyPy 提示交互题 Python 需注意:print(..., flush=True) 每次询问都会系统调用,次数上限内可接受;不要用 sys.stdin.buffer.read() 批量读入(会阻塞等待全部数据,交互器不回发造成死锁)。Grader 交互通常不提供 Python 版 grader 或头文件,需按题目给定接口实现,Python 在 CMS/IOI 环境支持有限,优先 C++。
时间复杂度查询次数:二分 O(log n),随机撒点 O(k + 期望长度),其他策略按题
空间复杂度O(n) 或 O(k)
注意 · 局限Grader 交互语言受限(CMS 通常只支持 C++/Pascal),Python 参赛者尽量选 STDIO 交互题;随机化策略有概率失败需按题目容错设计;交互题无法像普通题那样用对拍验证,正确性依赖静态推演与本地模拟交互器;交互次数耗尽后果严重(WA/ILE),实现要一遍对。

易错点

例题

OJ题号难度考点链接
Codeforces679A1400Bear and Prime 100:一次问 19 个质数及质数平方判定质合数,入门交互题https://codeforces.com/problemset/problem/679/A
Codeforces843B1800Interactive LowerBound:随机撒 1000 点逼近链表目标值,随机化交互https://codeforces.com/problemset/problem/843/B
UOJ2065【APIO2016】Gap:Grader 交互,分段/交互次数优化求最大间隔https://uoj.ac/problem/206
Codeforces750F2400New Year and Finding Roots:7 层完全二叉树上定位根,次数紧张需精妙策略https://codeforces.com/problemset/problem/750/F
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
上游 · 本知识点依赖(前置/变体/组合/共现)

信源

✓ = 已核验 · △ = 未核验