【前書き】
こんにちは。
ついこの前のABC266で緑色になりました!
ここまでの道のりは悔しい思いの連続でしたが、なんとか夏休み中の目標にしていた緑コーダーになることができました!参考までにAtCoder社長のchokudai氏の緑色の評価を載せておきます。
緑色になれれば、「競技プログラミングに熱心に取り組んでいる人」と考えて問題ないでしょう。要求レベルも決して低くなく、出場回数が足りないとマイナス補正がかかるため、運だけで到達することはまず出来ないラインです。他社アルゴリズム力判定サービスだと、上位1%の最高ランクが付く実力です。(あくまで「アルゴリズム力部分だけであることに注意してください)
印象としては、学生ならかなり優秀。
エンジニアとしてもある程度の安心感がある。論理的に複雑な処理の実装に対応できない、なんてことはなさそう、くらいには思える。データ量が多い現場など、計算量の多い処理が要求される現場でなければ、このレート帯以上を求める必要はほぼない。
くらいの印象です。もちろんアルゴリズム力しか計ってないので個人差があります。
こう言われると、なんか嬉しいですね(笑)。でも、僕の目標は青なのでまだ道半ばです。
本記事では、以下の4点についてお話しします。
1.僕のスペックと競プロとの出会い
2.レート、精進のお話
3.環境、マクロなど
4.辛い時のお話
では、本編行きましょう!!
【1】僕のスペックと競プロの出会い
大学:都内の私立大学理系の3年です。数学は高校までなら得意に入るかはわからないが、好きな教科でした。プログラミングを始めたのは大学2年の時で、Cをやらされて構造体とポインタで撃沈した普通の人です(大体みんなここで挫折するでしょ(笑))。
情報系の資格:何もない
こう見ると何して生きてたんだと突っ込みたくなりますが、話を戻します。僕が競プロと出会ったのは、大学3年生に上がる春休みの時です。
大学の授業で触ったプログラミングの知識を無駄にしたくないという思いから、なんとな〜くプログラミングについて調べているとAtCoderに出会いました。
環境構築さえすれば、UIやSQL、Gitなどの難しいことを学ばなくてもすぐに始められると知り、「とりあえずやるか」ぐらいの気持ちで始めました。初めての提出はPythonだったと思いますが(今はもっぱらC++です)、初めて"AC"という文字を見たあの高揚感は今でも覚えているものです。
【2】レート、精進、辛かった時のお話
【2-1】レートのお話
本格的に始めたのは3月くらいなので、緑に上がるまで半年もかかっているので、普通どころかむしろ少し遅いくらいだと思います。緑に上がる前のラスト2回のコンテストで合計195上げて805に到達しました。しかしそれ以外のコンテストでは、レートが下がっていることが多く、何度も自分には無理なのかと感じることがありました...。本当にきつかった(笑)。苦労話ばかりしていても仕方ないので、ここからはみなさまにとって一番有益な情報になるであろう話題、「精進」について話します。
【2-2】精進のお話
まず参考までに私が解いた問題数をお見せします。
綺麗に1000問!なんですが、本記事を書く前に2問だけACしたので本当は998問です(笑)。とは言いながらも、AOJも解いていたので1000問は確実に解いて緑になりました。よく色んな記事でAtCoderの過去問だけやるべきだとの主張をよく見かけますが、AOJのアルゴリズムコースは必ずやったほうがいいと思います。解くべき問題量の目安としては、「自分が目指したいレート×1.2」問解きましょう。だから緑になりたいなら1200問解いてください。頭のいい人なら400問とかでも行く人はいますが、気にするとしんどいので人と比べないようにしましょう(笑)
さて、ここからは私が思うやるべき精進を優先順位をつけて書いていきます。
①過去問演習
もうこれは言わずもがなでしょう。ABCは緑、水色レベルまでは典型の問題が多いと聞きます。つまりこの典型をいかに自分のものにできるかがレートの上昇に大きく貢献します。こう言われると難易度順に全て埋めていきたくなると思いますが、それは正直言って無駄です。A問題やB問題はコンテスト中にACできるなら全く演習しなくていいです。基本的なSTLや論理的思考力があればWAすることはあっても、そのままコンテストを終えてしまうことはないと思うので、ここからはC問題以降の精進について話します。
C問題は明確に出題者側からの意図が決まっています。それは「工夫して計算量を落として全探索を書けるか」です。ですが、全探索にも再帰関数を使った実装もあれば、forを回して条件整理する類の問題、DP(動的計画法)などバリエーションがありますが、逆に言えばそれくらいです。
なので、C問題の自分の色diff以降の問題は全部解きましょう。当然自分にとって解ける可能性が50%以下の問題を解いているのでわからないものがたくさん出てきます。ここで大事なことを言いますが、「30分経ってアルゴリズムや実装方針が思い浮かばない」ならすぐに解説を見てください!なぜならその問題はあなたが知らないアルゴリズムの典型である可能性が高いからです。そういう問題は教育的にそのアルゴリズムで解けてほしいというAtCoder側の意図なので、無理に解説を見ない縛りプレイは避けてください。先ほども述べましたがC問題はパターンが決まっているので、それをメタることが大事です。なのでC問題で頻出する典型アルゴリズムごとの精進方法とサンプルコードをまとめて、D問題の精進に移ります。
・再帰関数
再帰関数は慣れと言いますが、一理ありますが勉強しましょう(笑)。1番の勉強法は深さ優先探索(以下dfs)を再帰を用いて実装するコードをdebugして勉強しましょう。Stackの概念もあわせて勉強するといいと思います。参考までにサンプルコードを貼っておきます。帰りがけ順や行きがけ順、オイラーツアーあたりは一緒に覚えておくといいかと思います。
vector<int> Graph[MAX_N];
void dfs(int v, int p = -1) {
for(auto u : to[v]) {
if(u == p) continue;//もし訪れた先が葉ならfor文を抜ける
dfs(u,v);//そうじゃないなら潜る
}
}
・DP(動的計画法)
DPはとにかくナップザック系を理解しましょう。個数制限ありナップザック、個数制限なしナップザックの漸化式の意味は初心者には理解するのが難しい部分です。蟻本や他の方の記事をたくさん読んで是非自分のものにしましょう!また、snukeさんがよくおすすめしていますが、EDPC(Educational Dynamic Programming Contest)はM問題ぐらいまでは全部解きましょう。と言いたいですが、難しいのに解説放送がない....。こんな時はぜひ我らが「かつっぱ」さんのYoutubeチャンネルを見ましょう!すごくわかりやすいのでチャンネルに遊びに行ってみてください!参考までに個数制限ありナップザック問題のサンプルコードを載せておきます。個数制限なしの時は、遷移式のif(j-wei[i+1] >= 0) chmax(dp[i+1][j], dp[i][j-wei[i+1]]+val[i+1])がif(j-wei[i+1] >= 0) chmax(dp[i+1][j], dp[i+1][j-wei[i+1]]+val[i+1])に変わるだけです。理由は、DPテーブルの同じ計算をする部分を見ればすぐにわかります。詳しくは蟻本を見るといいでしょう。
# define rep(i,a,b) for(int i = a; i < (b); ++i)
# define srep(i, a, b) for(int i = a; i <= b; ++i)
# define ll long long
ll dp[110][110000];
//個数制限あり(EDPC D問題)
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,w;
cin >> n >> w;
//wei : 品物の重さ val : 品物の価値
vector<ll> wei(n+1), val(n+1);
srep(i,1,n) read(wei[i], val[i]);
//dp[i][j] i個めまで商品を買うか決めて重さがjになる時の価値の最大値
rep(i,0,n) {
rep(j,0,w+10) {
chmax(dp[i+1][j], dp[i][j]);
//i+1個目に選ぶ商品の重さの分だけ開けておく部分がj-wei[i+1]
if(j-wei[i+1] >= 0) chmax(dp[i+1][j], dp[i][j-wei[i+1]]+val[i+1]);
}
}
ll ans = -INF;
srep(i,0,n)srep(j,0,w) chmax(ans,dp[i][j]);
coout << ans << endl;
}
・Greedy(貪欲法)
この類の問題は、Greedyでなぜ解けるのかを考えましょう。本当は証明までできると望ましいですが、コンテスト中はそんな暇はないですし、確信を持てたらそれでいいと思います。(僕は証明までしたことはないです。)貪欲かを見抜くための重要な思考法は、「貪欲法が最適であることが明らかで、それ以外に最適解が存在することはあっても、貪欲に実行しても損をすることがないなら貪欲法が明らかに最適解である」という考え方です。これはこの類の問題を解いていくうちに分かるようになります。自信を持ってGreedyであると言えるようになるまでは、安易にGreedyであると決めつけないほうがいいです。
C問題についてはこれくらいですかね。次はD問題に移ります。
D問題からいよいよアルゴリズムの知識と実装力が必要になり、問題の球種も豊富になります。なので頭のいい人でも、多少は勉強していないとACできないと思います。逆に言えばここまで来れれば競プロを楽しめるようになるのではないでしょうか。僕自身、DFS,BFS,ダイクストラ,Union Find,クラスカル法,累積和,Imos法,二分探索などのアルゴリズムの知識に加えて、map,set,deque,queue,priority_queueなどのデータ構造をまともに使えるようになったのは、D問題のACを意識するようになってからです。
アルゴリズムの知識は解説放送でもsnukeさんが教えてくれますが、しっかり理解するには一度立ち止まって座学することが大切です。これをするかしないかで効率が段違いです。座学は必要に応じてすることが大事です。遭遇したことのないアルゴリズムを座学してもつまらないので出てきたらやりましょう。
問題の演習に関しては、僕は少なくとも全部3回は確実に解いています。バーチャル形式のものも含めれば5回を超える問題もあります。それくらい練習すると問題を読んでいるうちに、実装が思い浮かぶぐらいになります。それくらい繰り返してください。新規の問題を解くことも大事ですが、手を広げすぎるより、考えるけど手が動く問題をたくさんこなしましょう。AOJなどでUnion Findくらいは組めるようになっておくといいと思います。ランクの考え方は、エンジニアになる上では必要だと思います。
参考までにBFS, Unionfindあたりのサンプルコードを載せておきます。UnionFindは他に追加したい機能があれば追加してあげるつもりです。いつかacl/dsuを超えたいですね....
//BFS
//ここではグラフ上のノード0からの最短距離を求めています
# define rep(i,n) for(int i = 0; i < (n); ++i)
int main() {
const int INF = 1001001001;
int n;
cin >> n;
vector<vector<int>> Graph(n);
//make the graph
rep(i,n) {
int a,b;
cin >> a >> b;
--a;--b;
Graph[a].push_back(b);
Graph[b].push_back(a);
}
queue<int> q;
vector<int> dist(n,INF);
//BFSの時はこの関数を作ることをおすすめします
auto push = [&](int v, int d) {
if(dist[v] != INF) return;
dist[v] = d;
q.push(v);
};
push(0,0);
while(!q.empty()) {
int v = q.front(); q.pop();
int d = dist[v] + 1;
for(auto u : to[v]) push(u,d+1);
}
//3項演算子わかりづらいと申し訳ない...
rep(i,n) cout << (dist[i] == INF) ? "Can't visit" : dist[i] << endl;
}
//Union Find
//ここではUnion Findのstructを作っています。勉強になった思い出があります。
struct UnionFind {
//親の管理
vector<int> Parent;
//ランクの管理
vector<int> Rank;
int n;
UnionFind(n) : Parent.resize(n), Rank.resize(n) {}
void init() {
for(int i = 0; i < n; i++) Parent[i] = i;
for(int i = 0; i < n; i++) Rank[i] = 0;
}
int find(int x, int y) {
if(Parent[x] == x) return x;
else Parent[x] = find(Parent[x]);
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if(x == y) return;
if(Rank[x] < Rank[y]) {
Parent[x] = y;
}
else {
Parent[y] = x;
if(Rank[x] == Rank[y]) Rank[x]++;
}
}
bool same(int x, int y) { return find(x) == find(y); }
}
僕が自信を持って通せるのはDまでですね。Eは最近通せるようになってきました。水埋めの完了が9月の目標なので、バンバンEを通せるようになりたいですね。
②分野別初中級者が解くべき精選100問
過去問をある程度解き終えたら、必ずこれをやり切りましょう!僕は今の自分では解けなかった6問を除いて94問を2週間で集中して終わらせました。これが最後の2回のコンテストの結果に如実に出たと思います。この問題集は、水色コーダーになりたい人向けに作られていますが、緑や茶色の人がやるべき問題がたくさんあります。何よりレッドコーダーE869120さんが作ってくれている問題集ともあり、勉強になるものばかりでした。中でもJOIの問題は非常に作り込まれているので、時間をかけていいので自力でACできるように頑張ることをおすすめします。
③競プロ典型90問
こちらも②と同様に素晴らしい問題集だと思います。解説や類題も豊富なのでその点では②より進めやすいと思います。(かつっぱさんも実況してくれているので、参考になると思います。)
この問題集は、難易度ごとにカテゴライズされているので自分のいるレート帯より一つ下のレベルから一つ上のレベルまでをやり抜いて、レートが上がるごとに同じことを繰り返せば力がつくと思います。僕も10月中に星5までをやり抜くつもりです。
④バーチャル式の練習
競プロは時間内にどれだけ多くの問題を早く解けるかの勝負です。なのでいくら難しい問題を時間をかけてできたところで意味はないです。そこが開発や学校の課題とは大きく違います。AtCoder Problemsが提供するバーチャルコンテストの機能は、時間内に解くという意識づけをするのに最適です。朝活やくじかつに参加して強い人たちと戦うのもまた一興ですよ。
⑤数学のお勉強
これは緑の問題によくある話ですが、期待値や確率、整数や素数、高速な素因数分解、繰り返し二乗法、図形の回転、外積による図形の凹凸判定などゴリゴリの数学が出題されます。他人のライブラリをもらってきてメタるのも手ですが、一度座学をしてライブラリを作るなりその場で実装するなりしたほうがいいと思います。そのほうが楽しい(?)し、応用が効きます。まさにABC266では外積を利用した図形の凹凸判定が出題されましたね。僕もまだまだ勉強していきたい分野です。
精進に関してはこれくらいです。僕みたいに復習重視の人と、新規問題をどんどんやる人で精進のやり方は結構分かれると思うので、どちらのやり方も一度試してみて自分に一番合う精進法を見つけましょう。
【3】環境とマクロ
今のメイン環境は以下です。
M1 Macbook Air OS : Monterey
IDE Visual Studio Code
一時期にwindowsに慣れるために、sublimeとWSL2を使っていましたが、やはりM1の方が個人的に好みです。brew installで大体済むのはMacの特権ですね(笑)
環境構築の楽さは、個人的に圧倒的にMacが上です。Macを勧めてくれたN君には感謝しています(笑)
エディタはVSCodeにNeoVimのpluginを入れて使っています。正直今の時代は、JetBrainsのシリーズか、VSCodeの2択になると思います。GoとPythonの開発はGolandとPycharmを使っているのですが、VSCodeは何より拡張が豊富であることと、やっぱり軽いですね。起動時にノンストレスなのは流石すぎます。何よりバックにMicroSoftが付いているのが大きいですね(笑)
NeoVimは最高ですね。Vimmerの方々の気持ちが本当に理解できます(笑) いまだにEscを押すのを忘れるのでVimmerとは言えないですね。というかNeoVimの時点でVimmerではないか...
続いてマクロです。結構多い方かなと思います。参考までに良さそうなのがあったら使ってください。
整数系は持っておくと強いです。個人的にvectorをcin一髪でかけるのはおすすめです。良さそうなの探してみてください。
//My Macro
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
using namespace std;
using mint = modint998244353;
using C = complex<double>;
const int mod = 998244353;
const long long LINF = 1001002003004005006;
const int INF = 1001001001;
const double PI = acos(-1);
const int MX = 200005;
const int dx[4] = {-1,0,1,0};
const int dy[4] = {0,-1,0,1};
const int di[8] = {1,1,1,0,0,-1,-1,-1};
const int dj[8] = {1,0,-1,1,-1,1,0,-1};
int getint(){int x; scanf("%d",&x);return x;}
# define sz(x) (int)(x).size()
# define rsz(x,n) x.resize(n)
# define yes {puts("Yes"); return;}
# define no {puts("No"); return;}
# define dame {puts("-1"); return;}
# define yn {puts('Yes');} else{puts('No');}
# define ret(x) {cout << (x) << endl; return 0;}
# define ll long long
# define fi first
# define se second
# define be begin
# define en end
# define vi vector<int>
# define vl vector<long long>
# define vs vector<string>
# define vb vector<bool>
# define vm vector<mint>
# define vvi vector<vector<int>>
# define vvl vector<vector<long long>>
# define vvb vector<vector<bool>>
# define vpi vector<pair<int, int>>
# define vpl vector<pair<ll, ll>>
# define vps vector<pair<string, string>>
# define pb push_back
# define ps push
# define eb emplace_back
# define em emplace
# define pob pop_back
# define S sort
# define N next_permutation
# define rep(i,n) for(int i = 0; i < n; i++)
# define rrep(i,a,b) for(int i = a; i < b; i++)
# define srep(i, a, b) for(int i = a; i <= b; ++i)
# define drep(i, a, b) for(int i = a; i >= b; --i)
# define ALL(obj) (obj).begin(), (obj).end()
# define rALL(obj) (obj).rbegin(), (obj).rend()
# define _GLIBCXX_DEBUG
# define Pll pair<ll, ll>
# define P pair<int,int>
void CIN() {}
template <typename T, class... U> void CIN(T &t, U &...u) { cin >> t; CIN(u...); }
void COUT() { cout << endl; }
template <typename T, class... U, char sep = ' '> void COUT(const T &t, const U &...u) { cout << t; if (sizeof...(u)) cout << sep; COUT(u...); }
template<class T>bool chmax(T &a, const T &b) { if (a < b) { a = b; return 1; } return 0; }
template<class T>bool chmin(T &a, const T &b) { if (b < a) { a = b; return 1; } return 0; }
template<typename T>istream& operator>>(istream&i,vector<T>&v){rep(j,v.size())i>>v[j];return i;}
template<typename T>string join(vector<T>&v){stringstream s;rep(i,v.size())s<<' '<<v[i];return s.str().substr(1);}
template<typename T>ostream& operator<<(ostream&o,vector<T>&v){if(v.size())o<<join(v);return o;}
template<typename T>ostream& operator<<(ostream&o,vector<vector<T>>&vv){if(vv.size())o<<join(vv);return o;}
struct combination {
vector<mint> fact, ifact;
combination(int n):fact(n+1),ifact(n+1) {
assert(n < mod);
fact[0] = 1;
for (int i = 1; i <= n; ++i) fact[i] = fact[i-1]*i;
ifact[n] = fact[n].inv();
for (int i = n; i >= 1; --i) ifact[i-1] = ifact[i]*i;
}
mint operator()(int n, int k) {
if (k < 0 || k > n) return 0;
return fact[n]*ifact[k]*ifact[n-k];
}
};
ll gcd (ll x, ll y) {return x ? gcd(y%x, x) : y;}
ll lcm (ll x, ll y) {return x/gcd(x,y)*y;}
vector<pair<ll,int>> factorize(ll n) {
vector<pair<ll,int>> res;
for(ll i = 2; i*i <= n; ++i) {
if(n%i) continue;
res.eb(i,0);
while(n%i == 0) {
n /= i;
res.back().se++;
}
}
if(n != 1) res.eb(n,1);
return res;
}
ll binary_pow(ll a, ll n) {
if(n == 0) return 1;
ll x = binary_pow(a,n/2);
x *= x;
if(n%2) x *= a;
return x;
}
ll pascal[50][50];
void pascal_init() {
pascal[0][0] = 1;
rep(i,50) {
rep(j,i+1) {
pascal[i+1][j] += pascal[i][j];
pascal[i+1][j+1] += pascal[i][j];
}
}
}
vector<bool> prime_table(ll n) {
vector<bool> prime(n+1,true);
prime[0] = false;
prime[1] = false;
for(ll i = 2; i*i <= n; i++) {
if(!prime[i]) continue;
for(int j = i*i; j <= n; j += i) prime[j] = false;
}
return prime;
}
vector<ll> divisor(ll n) {
vl res;
for(ll i = 1; i*i <= n; ++i) {
if(n%i == 0) {
res.pb(i);
if(i*i != n) res.pb(n/i);
}
}
S(ALL(res));
return res;
}
C input_complex() {
double x, y;
CIN(x,y);
return C(x,y);
}
vector<pair<char, int>> runLengthEncoding(string s) {
int n = s.length();
vector<pair<char, int>> res;
char pre = s[0];
int cnt = 1;
rrep(i, 1, n) {
if (pre != s[i]) {
res.push_back({ pre, cnt });
pre = s[i];
cnt = 1;
}
else cnt++;
}
res.push_back({ pre, cnt });
return res;
}
vector<int> topologicalSort(vector<vector<int>> &G, vector<int> &inDegree, int nodenum) {
vector<int> res; //答え用の配列
priority_queue<int,vector<int>, greater<>> q; //入次数が0の頂点の処理待ち //辞書順が最小のものを返す
rep(i,nodenum) if(inDegree[i] == 0) q.push(i);
while(sz(q)) {
int v = q.top(); q.pop();
rep(i,sz(G[v])) {
int u = G[v][i];
--inDegree[u];
if(inDegree[u] == 0) q.push(u);
}
res.push_back(v);
}
return res;
}
template<class T>
vector<long long> dijkstra(vector<vector<pair<int, T>>>& graph, int start)
{
int n = graph.size();
vector<long long> res(n, 2e18);
res[start] = 0;
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> que;
que.push({0, start});
while(!que.empty())
{
auto[c, v] = que.top();
que.pop();
if(res[v] < c) continue;
for(auto[cost, nxt]: graph[v])
{
if(res[v]+cost < res[nxt]) {
res[nxt] = res[v] + cost;
que.push(Pll(res[nxt], nxt));
}
}
}
return res;
}
int main() {
}
BFSとかも追加するか迷ってます。何かいいのあったら是非教えてください。幾何ライブラリ作らないとな...
【4】競プロが辛くなった時のお話
精神面のお話です。これ結構大事です。コンテスト中全く手が動かなくなった時、自分の努力が身にならない感覚は結構きついです。僕も4,5週間連続で多ペナ3完、2完しかできないみたいな時がありました。そういう時に限って、友人やTwitter上の人は5完とか色変してたりするんですよね...。解説放送のコメントでもすごい人だらけで、結構な頻度で「自分センスないのでは?」と思ってしまいます。「もうやめよ」と思って布団に入ってみても、やっぱり悔しくて寝れないものです。布団の中で問題の解法を考えてしまうものです。でも強い人たちは当然僕よりも努力しています。5年かけてレッドコーダーになっている人はザラです。みんな言わないだけで血の滲むような努力の上にあの実力があります。そう思うと自分の悔しさはちっぽけに見えますし、素直に強い人に近づきたいと思えます。辛くなった時は、精進しろ。それが1番の近道。これ僕の友達が言ってました。
ここまでが本編です。拙い文章でしたが、これからは自分の覚書もかねて学んだことをアウトプットしていきたいと思います。読んでいただきありがとうございました!共に競プロ頑張りましょう!
以下は番外編です。「競プロは業務に必要ない」に対する僕の考えを述べてます。
競プロは業務に必要ない。これYoutubeとかでよく見る気がします。僕は今とある企業で業務をさせていただいています。実際にアルゴリズムを考えて実装するというよりは、先輩社員の方々が書いたコードのリファクタや部分的なアルゴリズム改変が主な業務です。初めてのチーム開発に加えて、Git、SQLの使い方を覚えて頑張っています。実際に社会に出て働くと個人開発ではあり得ない速さで成長できます。PRの御作法や2000行を超えるコードを読むことは、個人ではまずあり得ないですし、APIやAWSを扱うことも少ないと思います。そんな中で競プロの重要性を感じたことがあります。それは先輩社員の書いたコードをリファクタする際の実装方針に悩んだ経験があまりないところです。僕は競プロをやる前は実装方針を思いつくことが本当に苦手でした。無駄な計算をしているところなんて意識したことがなかったし、何がコーナーケースかを実装前に考えることはなかったです。コーナーケースを意識することは実務でも必要な能力です。例えばSQLでテーブルにINSERTするだけでは、重複レコードが存在したときにはエラーを吐きます。つまり重複する可能性があるならその処理はON CONFLICTなどを使ってUPSERTするべきです。このようにそのコードに隠れるエラーの危険性を見抜く能力は競プロをやっていれば、必ず養われます。この点で競プロは必要だと僕は思います。
また実務で計算量を意識するコードを書かなくていいのならば、なぜ大手のIT企業は必ず入社資格としてコーディングテストの合格を課すのでしょうか?それは世界をリードするサービスを作るには計算量を意識した、より効率の良いコードをかける必要があるからで、これは競プロを学ぶ意義そのものです。「アルゴリズムを考える」ことは一種の職人技であり、誰にでもできるものではないです。いろんな経験を積んできて初めて成せる技です。だからこそ価値のあるものだと僕は思います。プログラムが動いている裏ではどういう論理が動いているのかを理解できるエンジニアが作るサービスと、そうでないエンジニアが作ったサービスでは大きな差が生まれると思います。だからまずは毛嫌いせずにゲーム感覚でチャレンジしてみることが大事だと思いますし、競プロはむしろ、「プログラミングを好きになる最高の教材」だと僕は思います。大学生の青二才がなんか言ってるなぐらいのあったかい目で見守ってくださるとありがたいです(笑)
就職するまでには青コーダー以上になっておきたいし、GitとかSQLとかデータベース設計とかはマスターしたいですね。言語で言うと最近はGoがお気に入りなのでGo極めたいです。