競技プログラミング練習会
2020 Normal
第十三回(マラソン第一回)
担当:zeke
自己紹介
自己紹介(近況)
- KMCid:zeke(本名:岡島和希)
- 工学部工業化学科2回
- AtCoder水(1545)
- 先週のE問題みたいな実装辛いやつをACしていきたい
- もう夏休みってマジですか????
- 七夕7/7って旧暦のほうなんですね、知りませんでした
- #zeke-memo

マラソン競技とは?
マラソン競技
- 普通のコンテストみたいにACしたらOK!みたいなものではない。
- 得点が可変であり、できるだけ高得点を見つける競技
- 厳密な解法が存在するというよりかは、得点を稼ぐためにコードを改善させていくイメージ
マラソン競技の特徴
- とりあえずACするのは結構簡単なので幅広い層の人間が同じ問題に取り組める
- 制約に癖があることが多い
- 実装力が求められることが多い
- 結局どのくらい探索できるかがものを言うので高速な言語が強い(C++,Rust etc)
- コーディング以外のスキル(データ分析、ビジュアライザ、開発環境)が求められることがある
- 得点を高めるために様々な手法があることがある
マラソン競技開催主の例
- Top Coder Marathon Match
- Codingame
-
ハル研究所プログラミングコンテスト
- AtCoder
- 他
マラソンコンテストの例
今日は先日行われたIntroduction to Heuristics Contestにチャレンジしていこうと思います
今日やること
ビームサーチ!
やる前に
私のスキル
-
素人です(> <)
- マラソン競技にもまだ数回しか出た経験がなく、目下勉強中です
- スライドでも間違ったところがあれば、どんどん指摘ください
- みんなで知見を共有しながら、わいわいできたらいいと思います
AtCoderの配布している
- 入力生成器、スコア計算器、ビジュアライザ
- ↑はPython3で動く
- 環境を入れたほうが今後役立つらしい(第10回あーだこーだーより)
- 今後も↑は提供するらしい(同上)
- 使い方分からなかったら教えます
Visual Studio Live Share
- Visual Studio Codeで使える、コードを共同編集できる機能
- 実装がよくわからない人を効率的にサポートをしたいので、入れてくれると嬉しい…
- 強要はしません
- VScodeを持っている人はLive Shareという拡張を入れて、GitHubアカウントなどでログインしたら使えます
流れ
- 実装力に差があると思うので、スライドを読んでもらって実装していくうえで、よくわからないところがあったらサポートする形にしようと思います
- 実装に難があったら遠慮なく聞こう!(聞いてくれなかったら暇なだけなので本当に遠慮なく)
とりあえず
問題を読みましょう
問題概要
- 毎日、26種類のコンテストのどれかを選んで開催
- i日間にj種類目のコンテストを開催すると の満足度獲得
- 毎日すべてのコンテストに対して以下を差っ引く
\Sigma_{i=1}^{26} C_i(今日-最後にコンテストを開いた日)
S_{i,j}
つまり
- コンテストの満足度は開催日とコンテストに依存
- コンテストごとに開催日が離れすぎると、開催日の間隔の二乗が最終的に引かれてしまうのでよくない
- その日の満足度の高いコンテストを開きながらも、同じくらいの頻度でコンテストを開かないといけない?
- 探索空間は26^365とかになるので、到底全部見るのは無理
とりあえずACを取ってみましょう!
目標は一億点!
まずはB問題をACして評価関数を作りましょう
(難易度はABC-Bぐらい?実装するだけ)
解法1:貪欲解
貪欲解
らしい
ビームサーチの布石だったりする
イメージ
…
…
…
…
…
実装してみましょう!
うまくいくとたぶん62634806点が取れるはず?
実装ヒント
- 一日目から選ぶと得点が一番高くなるコンテストを選んでいく
- ↑priority_queueを使うといいかも?
- ゼロ日目は得点はゼロ
- いままで何を選んだのかを持つ配列と、あるコンテストが最後に開催された配列を持っておく
実装ヒント
long long evaluate(int contest, int date, vector<int> &prev_contest_date) {
// contest,date(何日目か),prev_contest_dateを渡す
/*評価計算*/
return /*その日の獲得得点を返す*/
}
int main(){
/*初手入力*/
vector<int> prev_contest_date(26,-1);
//それぞれのコンテストがいつ行われたか
vector<int> history;
//今まで何を選んだか
int res=0;
for(int date=0;date<D;date++){
priority_queue<pair<long long,int>> q;
//(獲得得点,コンテスト)を記録する
//priority_queue<pair>はpairの第一引数が降順になるように格納する
for(int contest=0;contest<26;contest++){
/*何かしらの処理をする*/
q.push({evaluate(contest, date, prev_contest_date),contest});
}
history.push_back(q.top().second+1);
res+=q.top().first
prev_contest_date[q.top().second]=date;
//記録する
}
/*出力*/
}
解法2:ビームサーチ
ビームサーチとは
- 先ほどの貪欲解だと、その日が終わったタイミングで最善のものだけを取っていた
- でも、最善じゃないやつ(2番目以降)でも、後々よくなるかもしれない
- それじゃ、上位X位のもの全部取ってくればよくない?
- ちなみに上位1位だけを取ってくるのが先ほどの貪欲法
イメージ

k
ビームサーチのいいところ
- 貪欲解と違い、「ちょっと悪いけど後々よくなる手」を採用できる
- 実装が比較的簡単?
- 幅(上位何位をとる)をいじることで、実行時間を簡単に調整可能
ビームサーチの苦手なところ
- 状態遷移(いわゆるDP)をするので、状態がたくさんあると大変(離散だといけるけど連続だと…)
- ↑つまり無向グラフだと辛い…
- あくまで貪欲の拡張なので、ずっと後になって結果が出てくる大器晩成型みたいなやつを見逃しやすい
- ↑多様性が出にくいという言い方をしたりもする
ビームサーチの応用
- 今回はスタートから順番に見ていくような形だったが、なんども周回をして、多点スタートをするchokudaiサーチというのがある←始点依存性が高かったりするときに有効
- 多様性を出すために、最初のほうはランダムで選ばれなかったところからコンテストを抽出、後半は上位X位に集中
実装ヒント
long long evaluate(int contest, int date, vector<int> &prev_contest_date) {
// contest,date(何日目か),prev_contest_dateを渡す
/*評価計算*/
return /*その日の獲得得点を返す*/
}
int main(){
/*初手入力*/
int res=0;
int width;
//ビームサーチの幅を決める
vector<tuple<long long,vector<int>,vector<int>>> width_info;
// tuple<現在までの獲得得点、それぞれのコンテストがいつ行われたか、今まで何を選んだか>
for(int date=0;date<D;date++){
for(auto state:width_info){
ll score = get<0>(state);
vector<int> prev_contest_date = get<1>(state);
vector<int> history = get<2>(state);
priority_queue<tuple<long long, vector<int>, vector<int>>> q;
//(獲得得点,コンテスト)を記録する
// priority_queue<pair>はpairの第一引数が降順になるように格納する
for (int contest = 0; contest < 26; contest++) {
/*何かしらの処理をする*/
q.push({evaluate(contest, date, prev_contest_date),prev_contest_date,history});
}
}
width_info.clear();
for(int i=0;i<width;i++){
/*幅の分だけ追加*/
}
}
/*出力*/
}さらに改善
改善
- これ以上点数を上げることはできないか?
- 幅を広げる
- 高速化
- ビジュアライザから何かしらの発見ができないか?
質問
来週からどうしますか?
来週
- いつも通りのコンテスト&講座やる
- 今日みたいなマラソン練習する
- 他
- 上ならやるアルゴリズムに希望があれば出してほしいです~(ネタ切れ)
- 下なら局所探索法などをやろうとおもいます
競技プログラミング練習会
By kmc_procon2020
競技プログラミング練習会
- 168