概要
この記事では、俺が競プロをしばらくやってみて「この話はもっと早く知りたかったな」みたいに思ったこととかを気分次第に紹介する
どこか別の場所で一度書いた文章を乱暴にコピペしてたりもするから、情報はあまり整理されてないと思って頂きたい
気が向いたときに編集・追記してく予定
また、今回の記事については、あくまで機能等の紹介であって解説のつもりではない
詳細な説明や使い方についてはここで解説するよりも読者が各自でググってくれた方が早いし分かりやすいと思うから割愛し、紹介程度に留めておく
質問等あればコメント欄とかSNSが繋がってる人はそっちで俺が分かる範囲でなるべく答えるから気軽にどうぞ
入門教材について
AtCoderで競技プログラミングをやるならAPG4bっていうAtCoder公式の教材がおすすめ
競プロを始めてしばらくはこれだけ勉強しとけば問題ないと思う
上記の教材にも書いてあるけど、基本毎週土曜日にやってるABCっていうコンテストは最初のうちは出ても基本的にレートが下がらないから積極的に出ておくと良いと思う
ちなみにABCはAtCoder Beginner Contestの略で、名前にBeginnerって入ってるけどちゃんと難しいしこのコンテストを完答できるのは化け物だから、最初のA問題しか解けなかったとかでも全然それが普通だから気にしなくて良いと思う
高いパフォーマンス・レートを出すためのTips
コードテストに関して
コンテストサイトには上の方のメニューバーに「コードテスト」という項目があり、コードを提出する前に試しに動かすことが出来る
コードを提出するときは一回コードテストとかでちゃんと動くか試してみると良い
入力例について
AtCoderの問題は入力例が複数ついてるから、それを使ってちゃんと正しい答えが出るか試してから提出するとWAの数が減ってレートも上がりやすくなる
特にA問題とかの序盤の問題は、提出まで時間によってパフォーマンスが結構大きく変わるから、WAして5分のペナルティを喰らうのは良いパフォーマンスを出したい場合は避けたい
もちろんレートを気にしないのであればWAの数なんて気にする必要ないんだが、気にする場合はこういう「良いパフォーマンスの出し方」みたいなのも意識するとより楽しめると思う
ARCに関して
ARC(AtCoder Regular Contest)という上位者向けのコンテストは、最初のうちは一問も解けなくてもとりあえずratedで参加しとくと"リセマラ防止のマイナス補正が緩む"ことによって結果的にレーティングが上がったりする
ただし、提出数が0だと放置扱いになるからWAでもなんでも少なくとも一回はコードを提出をする必要がある
使用言語に関して
AtCoderのコンテストにPythonで出場したいが、C++とかで出場した場合よりも実行時間が長くなり、TLE(実行時間制限超過)になりやすい、みたいなことはあるのか?
という質問を受けたことがある
実行するアルゴリズムにもよるが、C++の方が実行速度が何十倍も速いことも多いが、解説に書いてあるような良いコードを書いたら基本的に言語関係なくAC(正解)出来るはずではある
だが、大会によってはPythonが使えなかったり、解説がC++しかなかったりするから俺はC++が良いんじゃないかと思ってる
しかし、Pythonの方が言語の汎用性が高いのは事実だから、競プロをそんなに重視してなくてPythonを色んな用途で使う人ならPythonでも良いかもしれないが、競プロにおける使用人口ではC++に劣るから自分で解説のC++のコードを読み替えたりする必要が出てくる可能性も充分にある
それと、俺は競プロではPythonを使ってないから詳しくは知らんけど、PyPyっていうPythonの処理系を使うと大幅な高速化が望めるらしい
それでもC++と比べると10倍くらい遅かったりすることもザラにあるみたいだが
ちなみに、C++でもPythonでもない言語で競プロをやろうと思うと、競プロにおける使用人口がかなり少ないから、質問がしにくかったり自分が使ってる言語の解説がほぼ皆無になるっていうことは始める前に知っておいた方が良いかもしれない
参考までにPythonとPyPyとJavaの実行速度を比較した記事を貼っておく
https://qiita.com/Lily0727K/items/26d3e68d66a6a641b010
VSCodeの便利機能
競プロに限らず、プログラミングをするときにはコードを打ち込む場所であるテキストエディタが必要だが、世の中には数多のテキストエディタがある
俺は、拡張機能が多く、カスタマイズ性にも優れ、ネット上の情報も豊富なVSCodeの使用を勧めてる
ちなみに、VSCodeには配色テーマを変える機能もあったりするからアプリをカスタマイズするのが好きな人は「VSCode 配色テーマ」などでググると楽しいかもしれない
以下、VSCodeの便利な機能について軽く紹介する
ユーザースニペットについて
VSCodeにはユーザースニペットと呼ばれる便利な機能がある
ユーザースニペットを使うことで、よく使う定型的なコードをかなり楽に打つことが出来る
例えば、
int N; cin >> N;
vector<int> A(N, 0);
for(int i = 0; i < N; i++){
cin >> A[i];
}
というようなコードは競プロをやる以上、数えきれないほど書くことになるが、これを毎回全部打っていては効率が悪い
そこで、ユーザースニペットを
{
"intXcinX": {
"prefix": "ic",
"body": "int $1; cin >> $1;"
},
"vector_cin": {
"prefix": "vic",
"body": ["vector<int> $1($2, 0);", "for(int i = 0; i < $2; i++){", "\tcin >> $1[i];", "}"]
}
}
のように定義することで、下のGIFのように、簡単に整数型変数を宣言し、入力から受け取り、それを要素数とする配列を宣言し、入力から要素を受け取るコードを書くことができる
ユーザースニペットの詳しい機能や使い方、記述方法等についてはここで解説するよりも読者が各自でググってくれた方が早いし分かりやすいと思うから割愛するが、この記事なんかがおすすめ
特に、上記の記事の「複数行」と「可変値の入力」は使いこなしたい
ちなみに、上記の記事には載ってないが、Tabを出力したい場合はbodyに\t
と記述すれば良い
なお、「可変値の入力」の便利な使い方として、上記の記事では
最後のカーソル位置はテキストの末尾になるが、テキストの末尾以外にしたい場合は最終位置を$0で指定する。
という説明がされている内容だが、$1
や$2
ではなく$0
でカーソル位置を指定するとカーソル位置が定型文の末尾に飛ばないことを利用して、
#include <bits/stdc++.h>
using namespace std;
int main(void){
//定型文を出力した後、ここから書き始めたい
}//定型文を出力した後、書き始めたいのはここではない
のようなC++のテンプレなど、「定型文を出力した後に定型文の末尾ではない場所から書き始めたい場合」に$0
を使用すると便利である
この場合、JSONファイル(cpp.json)は、
{
"Cpp_template": {
"prefix": "cpp",
"body": ["#include <bits/stdc++.h>", "using namespace std;", "", "int main(void){", "\t$0", "}"]
}
}
のようになる
言葉で説明しても分かりにくいから自分で実際に$0
と$1
を使い比べてみると良いと思う
(おまけ)
具体例として、俺がC++で使ってるユーザースニペットのJSONファイル(cpp.json)の中身を以下に載せておく
[折りたたみ]
{
"Cpp_template": {
"prefix": "cpp",
"body": ["#define _GLIBCXX_DEBUG", "#include <bits/stdc++.h>", "using namespace std;", "typedef long long ll;", "template<typename T>bool chmax(T &a,const T &b){if(a<b){a=b;return true;}else{return false;}}", "template<typename T>bool chmin(T &a,const T &b){if(a>b){a=b;return true;}else{return false;}}", "/* ASCII", "[0:48],[1:49],[2:50],[3:51],[4:52],[5:53],[6:54],[7:55],[8:56],[9:57],", "[A:65],[B:66],[C:67],[D:68],[E:69],[F:70],[G:71],[H:72],[I:73],[J:74],[K:75],[L:76],[M:77],", "[N:78],[O:79],[P:80],[Q:81],[R:82],[S:83],[T:84],[U:85],[V:86],[W:87],[X:88],[Y:89],[Z:90],", "[a:97],[b:98],[c:99],[d:100],[e:101],[f:102],[g:103],[h:104],[i:105],[j:106],[k:107],[l:108],[m:109],", "[n:110],[o:111],[p:112],[q:113],[r:114],[s:115],[t:116],[u:117],[v:118],[w:119],[x:120],[y:121],[z:122]", "*/", "", "", "int main(void){", "\t$0", "}"]
},
"Cpp_template_mini": {
"prefix": "mcpp",
"body": ["#include<iostream>", "using namespace std;", "main(){", "\t$0", "}"]
},
"mini_permutation": {
"prefix": "mnp",
"body": "void permutation(vector<int>v){sort(v.begin(),v.end());do{for(int x:v){cout<<x<< ' ';}cout<<endl;}while(next_permutation(v.begin(),v.end()));}"
},
"permutation": {
"prefix": "np",
"body": ["void permutation(vector<int> v){", "\tsort(v.begin(), v.end());", "\tdo {", "\t\tfor(int x : v){", "\t\t\tcout << x << ' ';", "\t\t}", "\t\tcout << endl;", "\t} while(next_permutation(v.begin(), v.end()));", "}"]
},
"intXcinX": {
"prefix": "ic",
"body": "int $1; cin >> $1;"
},
"intXYcinXY": {
"prefix": "ic2",
"body": "int $1, $2; cin >> $1 >> $2;"
},
"intXYZcinXYZ": {
"prefix": "ic3",
"body": "int $1, $2, $3; cin >> $1 >> $2 >> $3;"
},
"intWXYZcinWXYZ": {
"prefix": "ic4",
"body": "int $1, $2, $3, $4; cin >> $1 >> $2 >> $3 >> $4;"
},
"strXcinX": {
"prefix": "sc",
"body": "string $1; cin >> $1;"
},
"strXYcinXY": {
"prefix": "sc2",
"body": "string $1, $2; cin >> $1 >> $2;"
},
"strXYZcinXYZ": {
"prefix": "sc3",
"body": "string $1, $2, $3; cin >> $1 >> $2 >> $3;"
},
"strWXYZcinWXYZ": {
"prefix": "sc4",
"body": "string $1, $2, $3, $4; cin >> $1 >> $2 >> $3 >> $4;"
},
"vecvecintXY": {
"prefix": "vvi",
"body": "vector<vector<int>> $1($2, vector<int>($3, 0));"
},
"vecvecintXYe": {
"prefix": "vvie",
"body": "vector<vector<int>> $1($2 + 1, vector<int>($3 + 1, 0));"
},
"vectorint": {
"prefix": "vi",
"body": "vector<int> $1($2, 0);"
},
"vectorstring": {
"prefix": "vs",
"body": "vector<string> $1($2, \"\");"
},
"vector_int_cin": {
"prefix": "vic",
"body": ["vector<int> $1($2, 0);", "for(int i = 0; i < $2; i++){", "\tcin >> $1[i];", "}"]
},
"vector_string_cin": {
"prefix": "vsc",
"body": ["vector<string> $1($2, \"\");", "for(int i = 0; i < $2; i++){", "\tcin >> $1[i];", "}"]
},
"vv_cin": {
"prefix": "vvic",
"body": ["vector<vector<int>> $1($2, vector<int>($3, 0));", "for(int i = 0; i < $2; i++){", "\tfor(int j = 0; j < $3; j++){", "\t\tcin >> $1[i][j];", "\t}", "}"]
},
"vve_cin": {
"prefix": "vvice",
"body": ["vector<vector<int>> $1($2 + 1, vector<int>($3 + 1, 0));", "for(int i = 1; i <= $2; i++){", "\tfor(int j = 1; j <= $3; j++){", "\t\tcin >> $1[i][j];", "\t}", "}"]
},
"forX": {
"prefix": "f",
"body": ["for(int $1 = 0; $1 < $2; $1++){$0", "}"]
},
"foreX": {
"prefix": "fe",
"body": ["for(int $1 = 1; $1 <= $2; $1++){$0", "}"]
},
"forforXY": {
"prefix": "ff",
"body": ["for(int $1 = 0; $1 < $2; $1++){", "\tfor(int $3 = 0; $3 < $4; $3++){$0", "\t}", "}"]
},
"forforeXY": {
"prefix": "ffe",
"body": ["for(int $1 = 1; $1 <= $2; $1++){", "\tfor(int $3 = 1; $3 <= $4; $3++){$0", "\t}", "}"]
},
"cout<<_<<endl;": {
"prefix": "cl",
"body": "cout << $1 << endl;"
},
"2Dindex": {
"prefix": "id",
"body": "$1[$2][$3]"
},
"endl;": {
"prefix": "ed",
"body": "endl;"
},
"sortVec": {
"prefix": "st",
"body": "sort($1.begin(), $1.end());"
},
"reverseVec": {
"prefix": "rv",
"body": "reverse($1.begin(), $1.end());"
},
"lower_bound": {
"prefix": "lb",
"body": "lower_bound($1.begin(), $1.end(), $2);"
},
"push_back": {
"prefix": "pb",
"body": "push_back($1);"
},
"all(obj)": {
"prefix": "all",
"body": "$1.begin(), $1.end()"
},
"ifYesNo": {
"prefix": "iyn",
"body": ["if($0){", "\tcout << \"Yes\" << endl;", "} else {", "\tcout << \"No\" << endl;", "}"]
},
"return0": {
"prefix": "r",
"body": "return 0;"
},
"max3": {
"prefix": "ma3",
"body": "max({$1, $2, $3})"
},
"min3": {
"prefix": "mi3",
"body": "min({$1, $2, $3})"
},
"setprecision": {
"prefix": "sp",
"body": "cout << fixed << setprecision(10);"
}
}
*マクロについて
C++にはマクロという、既存の関数に自分で名前をつけ、楽な記法を定義するというような機能があるが、マクロは単純に関数等の名前を置換するだけのものであり、例えその名前が既に使われているものであったりしても基本的にエラーを出さずに機能してしまう
つまり、変数のスコープや型も基本的に考慮してくれないし、言語が持っている「本来意図されていない動作をするコードに対してエラーを出力する」という安全機能が上手く働いてくれない
その以外にも、マクロは自分で任意の名前を定義するものであるから、マクロを多用すれば多用するほど書くコードの可読性が下がるというデメリットもある
rep(i, N)
くらいなら色んな人が使ってるから可読性にはそこまで影響しないが、逆に言えば何も注釈せずに問題なくコードに使えるマクロはrep(i, N)
くらいかと思われる
他人に自分のコードを読んでもらう機会が少なく、言語の動きを深く理解した上で時間短縮のためにマクロを使うのであれば問題はないのだが、競プロを始めてすぐは他人に自分のコードを見せて質問することもあるかもしれないと考えると、マクロには頼らずに上記のユーザースニペットを使うのが「コードを楽に速く書く」という目的を「マクロを使うことでコードの安全性と可読性を犠牲にする」ことなく達成できる折衷案なのではないかと俺は思っている
コードの自動整形について
VSCodeの非常に便利な機能として、コードの自動整形も挙げられる
コードを書く際、適切なインデントをしないと、コードが長くなればなるほどコードの入れ子構造などが分かりにくくなってしまうため、適切なインデントはコーディングをする以上必須であると言えるが、人によってはこのインデントがどうにも面倒らしい
そこで、VSCodeの自動整形機能が非常に便利である
自動整形機能を使うことで、ショートカットキー(デフォルトではWin : Shift + Alt + F, Mac : Shift + option + F)を一回押すだけでコード全体を自動で整形してくれる
また、自動整形をしてくれるフォーマッタをカスタマイズすることで自動整形の設定を自分好みに変えることも出来る
参考 : https://pystyle.info/cpp-how-to-use-clang-format/
なお、VSCode標準搭載の自動整形機能ではなく、拡張機能としてフォーマッタや自動整形ツールをインストールするという選択肢もある
自動整形機能の使い方等についてはここで解説するよりも読者が各自でググってくれた方が早いし分かりやすいと思うから割愛し、紹介程度に留めておく
コーディング補助AI「Tabnine」について
上記のユーザースニペットに加えて、コーディング補助AIを使うとコーティングがより楽になると思うから、具体例としてTabnineというコーディング補助AIを紹介する
このAIは以下の画像のように、コードを途中まで入力するとその先を予測して変換候補として提示してくれる(変換候補をこの表示方法にするにはこの設定が必要)
Tabnineについても設定等についてはここで詳しく解説するよりも読者が各自でググってくれた方が早いし分かりやすいと思うから割愛し、紹介程度に留めておく
(参考) VSCodeでTabnineを動かす方法について解説してる記事 :