src/main.cpp
に解答を書く方法の説明です。この説明は src/main.cpp
を見ながら読んでください。
実行時間を計るために使うクラスです。solve
関数の先頭でこの型のオブジェクトを構築して計測を開始します。
実行時間が制限時間に達していない場合に true
を、そうでない場合に false
を返します。制限時間まで処理を繰り返し行いたい時には以下のようにします。
const utility::timer tm;
while (tm.good()) {
// 処理
}
実行時間が制限時間の a/b 倍に達していない場合に true
を、そうでない場合に false
を返します。制限時間の半分まで処理 A を、それ以降は制限時間まで処理 B を行いたい時には以下のようにします。
const utility::timer tm;
while (tm.frac<1, 2>()) {
// 処理 A
}
while (tm.good()) {
// 処理 B
}
経過時間を ms の単位で除算した値を返します。
const utility::timer tm;
// 300 ms 間待機
std::this_thread::sleep_for(std::chrono::milliseconds(300));
auto t = tm.elapsed(); // t は 300 に近い値
解として出力するものを保持するクラスです。
- 76 行目の
time_limit
の値を問題の実行時間制限に合わせて変更します(単位は ms)。20 ms 程度は余裕を持たせることをおすすめします。 - 126 行目の
threads
の値を使っている PC に搭載されている CPU のスレッド数(またはそれより少し小さい数)に変更します。 - 93 行目の
struct result
が解答として出力する内容を表すようにコードを編集します。例えば AHC001 では出力するものが 4 つの数の配列なのでなどとします。struct result { std::vector<int> a, b, c, d; };
struct result
のすぐ下にあるprint
関数を、解答を表す変数res
を受け取ってその内容をos
に出力するように書き換えます。std::cout
の代わりにos
を使うのだと考えてください。AHC001 の例ではなどとします。void print(std::ostream& os, const result& res) { for (std::size_t i = 0; i < std::size(res.a); i++) os << res.a[i] << ' ' << res.b[i] << ' ' << res.c[i] << ' ' << res.d[i] << '\n'; }
solve
関数内の// declare variables
と書いてある部分で入力する変数の宣言を行い、その下にあるラムダ式scan
の中に入力をis
から読むコードを書きます。std::cin
の代わりにis
を使うのだと考えてください。AHC001 の例ではなどとします。// declare variables int n; std::vector<int> x, y, r; const auto scan = [&] { // read inputs is >> n; x.resize(n); y.resize(n); r.resize(n); for (int i = 0; i < n; i++) is >> x[i] >> y[i] >> r[i]; };
// initialize solution
と書いてある部分で解の初期化を行います。解は新たに宣言するのではなく、solve
関数の引数として与えられているresult
型の変数res
を使ってください。AHC001 の例で、面積が 1 の正方形を初期解とするならなどとします。// initialize solution res.a.resize(n); res.b.resize(n); res.c.resize(n); res.d.resize(n); for (int i = 0; i < n; i++) { res.a[i] = x[i]; res.b[i] = y[i]; res.c[i] = x[i] + 1; res.d[i] = y[i] + 1; }
// improve solution
と書いてある部分で解の改善を行います。ただし後述の通りこのテンプレートはある程度改変しても構いません。
このプログラムは以下のように実行されます。この流れは main.cpp
に既に書かれているので新たに書く必要はありません。
main
関数でresult
型のオブジェクトを宣言するmain
関数がそれを参照渡しでsolve
関数に渡すsolve
関数がそこに解を書き込んでmain
関数に返すmain
関数がprint
関数を呼んで解を出力する
- デフォルトでは必要最低限のライブラリしか include されていないので、include 文は必要に応じて追加してください。
をしたい人はしてもよいです。
#include <bits/stdc++.h> using namespace std;
solve
関数内で標準出力に解を出力するコードを書く必要はありません。求解が終わるとprint
関数が呼ばれて解が出力されるようになっています。- 求解のプログラムは並列で実行されるので、競合(一つの変数に複数のスレッドが同時に書き込みを行うなど)が発生しないように気をつけてください。
- 例えば、以下のことは行ってよいです。
- 関数内でローカル変数を宣言・使用する
- グローバルに定数を宣言して読み取り専用の値として使用する
- 内部で状態を保持しない関数 (競技プログラミングで書くような関数の多くはそうです) を作成して使用する
static
修飾された変数を持たないクラスを作成して使用する
- 例えば、以下のことは行わないでください。どうしても行う必要がある場合は入力の読み込みのように
safe_invoke
関数を通して行えばよいですが、テストを並列で実行する際にテスト間で同じ変数が共有されることになるので、プログラムの挙動が想定通りにならない可能性があります。- グローバルに変数を宣言し、その変数に書き込みを行う
static
修飾された変数を用いた関数やクラスを作成して、その変数に書き込みを行う
- 例えば、以下のことは行ってよいです。
- このテンプレートはある程度改変して使っても構いませんが、
END_MAINLOOP
やEND_SOLVE_FUNC
を書く位置には気をつけてください。求解を複数のステップに分け、複数のループを回す場合にはのように// 実行時間が制限時間の 1/3 になるまで処理 A をする while (tm.frac<1, 3>()) { // 処理 A END_MAINLOOP; } // 実行時間が制限時間の 2/3 になるまで処理 B をする while (tm.frac<2, 3>()) { // 処理 B END_MAINLOOP; } // 実行時間が制限時間になるまで処理 C をする while (tm.good()) { // 処理 C END_MAINLOOP; } END_SOLVE_FUNC;
END_MAINLOOP
を複数書くことになる場合があります。END_SOLVE_FUNC
は常にsolve
関数の末尾に 1 つだけ書いてください。途中解のスナップショットの取得はEND_MAINLOOP
やEND_SOLVE_FUNC
に到達した時に行われるので、この時にres
の内容は (0 点でもいいので) 解として正常な状態である必要があります。例えばEND_MAINLOOP
に到達した時にres
の内容が未初期化である場合には未初期化の変数へのアクセスが発生し未定義動作が起きます。