交互题
交互题
前言
所以 \(221\) 同学为啥子要学交互题呢?
为了APIO2023不爆零(\(\texttt {APIO 38pts 2023.07.05 补档}\))
引入
交互题,顾名思义,要求你的程序与命题人的程序(通常称为交互库)进行互动,来解决问题
引用 \(Codeforces\) 的话
This is an interactive problem. Remember to flush your output while communicating with the testing program.** You may use fflush(stdout) in C++, system.out.flush() in Java, stdout.flush() in Python or flush(output) in Pascal to flush the output.
命题意图
- 解决实际问题。你不能获得所有的信息,只能通过若干个给出的接口向交互库提出问题,来得到你需要的答案。
- 强制在线。强制在线一般有两种方式,对输入数据进行加密(常为异或上次答案),或者进行交互。交互题的特点决定了你每次只能获取一次询问,并返回答案,才能获取下一次询问。
- 国外信息学竞赛(例如 \(APIO\))的题目往往不用你实现整个程序,而是实现几个接口函数,这类题目与交互题类似。
- 虽然这是道传统题,但我就是要出成交互,我爱交互!!这种出题人……这题出的很好,下次别出了,祝 \(TA\) 身体健康。
交互方式
一般交互题有俩种交互方式:
- \(Grader\) 交互(函数式交互):给你提供几个函数接口,向交互库提问时调用;同时你需要实现几个函数接口,供评测时获取你的答案用
- \(I/O\) 式交互:通过标准输出流向交互库提问或给出答案,从标准输入流读入输入信息和提问的回答。
接下来分别介绍这俩种交互方式
\(Grader\) 交互方式
题目会给你一个必须包含的头文件和几个函数接口,你需要实现另外要求的函数接口
一般来讲,好的题目描述会通过如下方式给出:(仅为举例,不一定有实际意义)
- 请在程序中包含
array.h
头文件。 - 你可以调用一个函数
int compare(int x, int y)
,返回值为:若 \(x \lt y\) ,返回 \(-1\) ;若 \(x \gt y\) ,返回 \(1\) (这个函数接口没啥意义,但是一个挺好的例子) - 你需要实现一个函数
void init(vector<int> A, int n)
,接收输入数据。这个函数会在程序开始时被调用恰好一次 - 你需要实现一个函数
int find_minimum(int l, int r)
,返回值为 \(A\) 中下标在 \([l,r]\) 的数中最小数的值,这个函数会被调用不超过 \(q\) 次
此时你可以实现下面一个程序:
#include "array.h"
#include <bits/stdc++.h>
using namespace std;
void init(vector<int> A, int n) {
; // 什么都不做
}
int find_minimum(int l, int r) {// 这么做是没有意义的,只是一个例子
if (compare(l, r)) return 0; // 当区间长度大于 1 时,返回 0
return 1; // 当区间只有一个数时,返回 1
}
注意你不需要,也不应当实现主函数
在提交前仔细检查是否包含头文件、是否删掉了主函数
请注意这个例子与实际比赛中 Grader 交互题的不同:因为洛谷交互题实现原因,这道题没有要求包含头文件,而实际比赛中往往需要
I/O 式交互
这类交互在实际比赛中较少用到(除了 \(CodeForces\) 等线上平台的比赛),建议重点掌握 \(Grader\) 交互,这种也要会
题目会给你你的询问方式,并给出你答题的方式
举一个猜数的例子:
- 首先你要读入一个整数 \(n\) ,表示待猜的数在 \([1,n]\) 中。
- 你可以通过
? x
的方式向交互库提问,随后读入一个整数表示回答。如果 \(x\) 小于待猜的数,交互库回答 \(−1\) ;如果 \(x\) 等于待猜的数,交互库回答 \(0\) ;如果 \(x\) 大于待猜的数,交互库回答 \(1\) - 当你猜出这个数后,你可以通过
! x
给出答案,\(x\) 即为这个数
注意 I/O 交互中你每次输出需要刷新输出缓冲区,对于 \(C/C++\) 语言可以 fflush(stdout);
对于 \(C++\) 语言可以 cout<<endl;
你可以实现这样的程序:
#include <bits/stdc++.h>
using namespace std;
signed main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
printf("? %d\n", i);
fflush(stdout);
int x;
scanf("%d", &x);
if (!x) {
printf("! %d\n", i);
return 0;
}
}
return 0;
}
当然这个程序会超时,只是作为例子我懒得写二分
关于调试
比赛时如果出题人给出了 \(grader\) 头文件(用于 \(grader\) 交互题的调试)或者 \(checker\) 程序(用于 \(I/O\) 交互题的调试),则交互题的调试比较简单,因为交互题的对拍会比普通题目的对拍困难很多。没有 testlib.h
的情况下。交互细节较多的题目的 stdio 交互库会一般有 \(3k\) 代码量,再加上 \(3k\) 长度的对拍器,至少需要一小时实现。但是,无论是否有调试程序,调试交互题的代码都往往需要选手模拟与程序的交互过程,因此交互题需要选手能设计出高质量的程序,尽量保证一遍做对,同时拥有较强的静态查错能力。