インタラクティブ問題を解くスタイルについての,自分用のメモ. いろいろ試行したが,現状 (2023年4月現在) はこれになっている.
running example
長さ $N$ のソート済みの整数列 $P_1, \ldots, P_N$ が隠されている. $N$ と $x$ が最初に与えられる. 「$i$ 番目の整数は何か」との質問を,たかだか 10 回おこなって,$P_i = x$ となる $i$ を答えよ. 存在しない場合には -1 と答えよ.
- 質問の形式:
? i
- 答の形式:
! i
制約: $N \leq 1000$,$P_i < P_j (i < j)$,
コーディング
特別なサポートは行っていない.
質問用の関数と解答用の関数は用意しておくと良い.
ll ask(ll i) {
cout << "? " << i + 1 << endl; // 0-index 等はここで調整
ll x; cin >> x;
return x;
}
void fin(ll i) {
cout << "! " << (i >= 0 ? i + 1 : -1) << endl;
exit(0);
}
main 関数は,ask() と fin() を使って書く.
int main(/* int argc, char *argv[] */) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout << setprecision(20);
ll N, x; cin >> N >> x;
ll lo = -1, hi = N;
while (true) {
if (lo + 1 == hi) fin(-1);
ll mid = (lo + hi) / 2;
ll y = ask(mid);
if (y < x) lo = mid;
else if (x < y) hi = mid;
else if (x == y) fin(mid);
}
return 0;
}
ジャッジプログラム
デバッグするとなると,やはりジャッジプログラムが必要になると思われる. ジャッジプログラムを動作させるスクリプト interun を作ってある. この環境で動かすためには,ジャッジプログラムは次のように作成する.
- 問題を標準入力から読み込む.
- 問題のフォーマットは自分で適当に決める.
- 問題ファイルを din_x.txt として用意する.
- 解答プログラムとのやりとりも,標準入出力を使う.
- ユーザに読ませたい出力は,行頭に
#
を置いて (標準エラー出力ではなく) 標準出力に出す. - judge という名前で実行するようにしておくと良い (オプションで指定も可能)
次を定義しておくと良い.
void ac() {
cout << "# AC" << endl;
exit(0);
}
void wa(string msg = "") {
cout << "# WA: " << msg << endl;
exit(1);
}
main を書く.正解したら ac() を,不正解なら wa() を呼ぶ.
int main() {
ll N, x, ans; cin >> N >> x >> ans; // 解答も与えられると想定
vector<ll> P(N); REP(i, 0, N) cin >> P[i];
cout << N << " " << x << endl;
ll cnt = 0;
while (true) {
string tp; cin >> tp;
if (tp == "?") {
if (cnt++ >= 10) wa("Exceeds Limit");
ll i; cin >> i;
cout << P[i - 1] << endl;
}else if (tp == "!") {
ll i; cin >> i;
if (i == ans) ac(); else wa();
}else assert(0);
}
}
#
で始まる出力は,ユーザ向けである.
interun は,両プログラムのやりとりをログファイル (デフォルト a.log)
に出力するので,誤答の場合に渡された値などは必ずしも出力しなくても良い.
実行
din_?.txt
ファイルを用意する.
ここでは,正解もファイルに書き込まれているという想定としているが,
ジャッジプログラムの作り方によっては不要にもできる.
5 30 4
1 10 15 30 100
実行する:
$ interrun < din_1.txt
cans
, judge
, a.log
の名前は interun のオプションで変更できる.
cmpNaive (ランダムテスト)
デバッグ時の cmpNaive では, 解答プログラム cans と ジャッジプログラム judge は別プロセスとの想定である. スレッド対応はしていない.
$ cmpNaive -e -i -p $AtCoderTop/clib/tool/interun
interun は,judge プログラムと同じ終了コードを返すので,
-e
(終了コードでの判定), -i
(独立プロセス) を指定して,
interun の実行を指定して cmpNaive を実行すれば良い.
interun にオプションを指定したいときには次のようにする:
$ cmpNaive -e -i -p "$AtcoderTop/clib/tool/interun -j ./my_judge"
keyword: interactive