2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++&Ubuntu(WSL2)で快適な競プロ環境を構築

2
Posted at

はじめに

最近、AtCoderを再開しようと思い、競プロ環境を見直した結果、様々な改善が行えたため、備忘録も兼ねて紹介したいと思います。
想定読者は以下の通りです。

  • 競プロの環境を1から構築したい方
  • 既にC++競プロを行っているが、Linux環境に移行したい方
  • 既にLinux&C++で競プロを行っているが、競プロ環境をより良くしたい方
  • 未来の俺

この記事で扱う内容

  1. C++(GCC)の導入
  2. 作業ディレクトリの作成
  3. AtCoder Libraryの導入
  4. bits/stdc++.hatcoder/allヘッダーのプリコンパイル
  5. シェル関数による実行の高速化
  6. 競プロ用テンプレートの構築(C++)
  7. ブラウザ拡張機能の導入
  8. もっと快適にしたい場合

この記事で扱わない内容

  1. VSCodeの導入 (Vimを愛しているため)
  2. ojによる自動テスト&CLI提出(まだ使ったことがないため)
  3. これ

タイトルではUbuntuとしていますが、Ubuntu以外のLinuxディストリビューションや、同じUNIX系であるMacOSにおいても、使用コマンドの違いなどはありますが、概ね同様に構築することが可能だと思います。

C++(GCC)の導入

すでに、WSL2の導入などにより、Ubuntu環境が構築できているものとします。

C++(GCC)の実行環境は以下のコマンドによって導入できます。

sudo apt update
sudo apt install build-essential

ここでg++のバージョンを確かめてみましょう。

g++ --version

今回はバージョンを指定せずにインストールしたため、環境によってそれぞれのバージョンが入っていると思います。(筆者のWSL2では11.4.0)

バージョンについて

Atcoderの使用できる言語とライブラリの一覧によると、C++(GCC)はC++23(GCC15.2.0)で実行されているようです。

ここで、C++のバージョンというのは言語仕様のバージョンであり、GCCのバージョンというのはコンパイラのバージョンです。
C++23は新しいバージョンであるため、その言語仕様はコンパイラが新しくなるにつれて徐々に反映されていき、新しいコンパイラであるほど、C++23をより完全な状態で使えるということになります。

実際、多くの競プロerが使っている機能は高々C++17までで、GCC9以上であればC++23(GCC15.2.0)に対応しなくとも困る場面は少ないのですが、せっかくなのでAtCoderの環境と同じバージョンでの実行環境も用意しておくと良いでしょう。

新しいバージョンになるほどコンパイル時間を要する傾向にあるため、状況に応じて使い分けると良いと思います。

C++23(GCC15)の導入

以下のコマンドでGCC15を導入できます。

# Ubuntu24以下の場合必要
sudo add-apt-repository ppa:ubuntu-toolchain-r/test

sudo apt update
sudo apt install g++-15

※ Ubuntu24以下では、aptの標準リポジトリにg++-15が含まれていないため、sudo add-apt-repository ppa:ubuntu-toolchain-r/testによって、g++-15が含まれる拡張リポジトリを追加しています。

以下のコマンドでC++23が使えることを確認しましょう!

echo -e '#include <print>\nint main(){std::println("Hello, World!");}' > main.cpp
g++-15 -std=gnu++23 main.cpp && ./a.out

std::printlnというC++23の機能で"Hello World!"と出力しています。

作業ディレクトリの作成

次は、実際にコーディングを行うためのディレクトリを作成していきます。
コンテストごと、問題ごとに作成するという流派もあるようですが、私は3ファイルを使いまわして運用しています。(libは競プロ用の自作ライブラリです。)
image.png
ディレクトリは様々な構成が考えられますが、私と同じ構成にする場合、以下のコマンドを実行してください。

mkdir -p ~/code/atc
cd ~/code/atc
touch {a..c}.cpp

AtCoder Libraryの導入

AtCoder Libraryは、UnionFindやセグメント木といった競プロ頻出のアルゴリズム・データ構造が含まれたライブラリです。(AtCoder Libraryドキュメント)

これを導入すると、プログラムで#include <atcoder/all>とするだけで、AtCoder上でこれらのライブラリを使用できるようになります。

githubからダウンロードすることができるのですが、実際に使うには、

  1. ac-libraryをC++のInclude Pathが通ったとこに置く
  2. ac-libraryをC++のInclude Pathに追加する
  3. 実行時に毎回、-Iを用いてac-libraryのパスを指定する

のいずれかが必要で、今回は2の方法を紹介しようと思います。

ac-libraryのダウンロード

ac-libraryはどこに配置しても良いですが、私は~/lib配下に配置しているため、今回は~/libに配置する前提で進めていきます。
以下のコマンドを実行し、ac-libraryをダウンロードします。

mkdir -p ~/lib
cd ~/lib
git clone https://github.com/atcoder/ac-library.git

CPLUS_INCLUDE_PATHへの追記

また、ac-libraryをどこでも使えるようにするため、CPLUS_INCLUDE_PATHに追加します。

bashを使っている場合、(= echo $SHELL/bin/bashと出る場合)

echo 'export CPLUS_INCLUDE_PATH="$HOME/lib/ac-library:$CPLUS_INCLUDE_PATH"' >> ~/.bashrc
source ~/.bashrc

※ zshの場合は、bashrcをzshrcに置き換えてください。

動作確認

以下のファイルを作成して実行してみましょう。

a.cpp
#include <iostream>
#include <atcoder/all>

using mint = atcoder::modint998244353;

int main() {
    mint a = 2000000000; 
	std::cout<<a.val()<<std::endl;
}
g++ a.cpp && ./a.out

3511294と出力されたら成功です。

bits/stdc++.hatcoder/allヘッダーのプリコンパイル

ヘッダーファイルのプリコンパイルを行うことで、コンパイル時間の短縮ができます。
私の環境だと、1.2秒から0.5秒まで短縮できました。(C++17の場合)
C++23の場合、プリコンパイルしても0.8秒と少し遅くなります。
0.3秒の違いですが、意外と大きく感じたため、これらを使い分ける方法も後ほど紹介したいと思います。

プリコンパイルの方法

まずは、単にC++17の場合のみに対してプリコンパイルを適用する方法です。
プリコンパイルはプログラムの中で1つのヘッダーに対してしか適用できません。
なので、以下のように記述しても、先に書かれてるbits/stdc++.hしかプリコンパイルの恩恵を受けられないことになります。

#include <bits/stdc++.h>
#include <atcoder/all>

この解決策として、<bits/stdc++.h><atcoder/all>をincludeする独自のヘッダー(acl_bits.hppとします)を作成し、acl_bits.hppのみincludeするという方法があります。
そして、acl_bits.hppに対してプリコンパイルを適用することで、<bits/stdc++.h><atcoder/all>双方のプリコンパイルの恩恵を受けることができます。

先ほどのac-libraryのディレクトリに移動し、さっそくacl_bits.hppを作成しましょう。

cd ~/lib/ac-library
echo -e '#include <bits/stdc++.h>\n#include <atcoder/all>' > acl_bits.hpp

続いてプリコンパイルを行い、gchファイルを生成します。

g++ acl_bits.hpp

ディレクトリ内に、acl_bits.hpp.gchが生成されているはずです。
任意のC++ファイルで、acl_bits.hppをincludeし、-Hオプションをつけてコンパイルしてみましょう。

~/code/atc
$ g++ -H a.cpp
! /home/fibon/lib/ac-library/acl_bits.hpp.gch
 a.cp

!マークはプリコンパイルヘッダーが適用されたことを意味します。

プリコンパイルを適応するには、プリコンパイル時と実行時のコンパイルの条件(g++のバージョン、最適化オプションなど)が一致する必要があります。

提出時の切り替え

AtCoderのジャッジ環境にacl_bits.hppはないため、これの有無を判定して、includeの切り替えを行います。
C++ファイルの先頭のincludeの部分で、以下のようにプリプロセッサ命令を用いた条件分岐を使います。

#if __has_include(<acl_bits.hpp>)
#include <acl_bits.hpp>
#else
#include <bits/stdc++.h>
#include <atcoder/all>
#endif

これを行うことで、ローカル環境では、acl_bits.hppによるコンパイル時間の高速化、ジャッジ環境では通常のincludeといった切り替えが行えます。

コンパイル条件ごとにプリコンパイルヘッダーを使い分ける

上記の方法では、1つのヘッダーに対して、1つのコンパイル条件下でのプリコンパイルヘッダーしか作成できません。
そこで、C++17とC++23、最適化オプションの有無といった様々な条件の違いがあっても、それぞれのプリコンパイルヘッダーが適用されるようにしたいです。

それを実現するには、単一のacl_bits.hpp.gchファイルを使うのではなく、acl_bits.hpp.gchをディレクトリとし、その配下にそれぞれのgchファイルを配置するといいう方法を用います。

名前の衝突を避けるため、一旦先ほどのacl_bits.hpp.gchは削除し、ディレクトリとして作成します。

~/lib/ac-library
rm acl_bits.hpp.gch
mkdir acl_bits.hpp.gch

この中にそれぞれのgchファイルを作成します。ファイル名に特に決まりはありません。

C++17(GCC11)とC++23(GCC15)の2つで作成

~/lib/ac-library
g++ acl_bits.hpp -o acl_bits.hpp.gch/g++11.gch
g++-15 -std=gnu++23 acl_bits.hpp -o acl_bits.hpp.gch/g++15.gch

再び-HオプションでC++ファイルをコンパイルし、プリコンパイルが適用されているか確認してみます。

~/code/atc
$ g++ -H a.cpp
! /home/fibon/lib/ac-library/acl_bits.hpp.gch/g++11.gch
 a.cpp
$ g++-15 -std=gnu++23 -H a.cpp
x /home/fibon/lib/ac-library/acl_bits.hpp.gch/g++11.gch
! /home/fibon/lib/ac-library/acl_bits.hpp.gch/g++15.gch
 a.cpp

それぞれのコンパイル条件にあったプリコンパイルファイルを見つけて適用してくれています。

シェル関数による実行の高速化

コンパイル&実行は以下のように行なえますが、コード実行のたびにこれを打つのはタイムロスです。

g++ a.cpp && ./a.out

そこで、シェル関数を用いて以下のように実行できるようにします。

g a

これは、.bashrcに以下の関数を定義することで実現できます。

.bashrc
g(){
  g++ "$@".cpp && ./a.out
}

.bashrcへの追記&適用は以下のコマンドで行えます。

echo -e 'g(){\n  g++ "$@".cpp && ./a.out\n}' >> ~/.bashrc
source ~/.bashrc

また、g23でC++23に切り替えられると便利なので、以下の関数も定義します。

.bashrc
g23(){
  g++-15 -std=gnu++23 "$@".cpp && ./a.out
}

同様に、.bashrcへの追記&適用は以下のコマンドで行えます。

echo -e 'g23(){\n  g++-15 -std=gnu++23 "$@".cpp && ./a.out\n}' >> ~/.bashrc
source ~/.bashrc

競プロ用テンプレートの構築(C++)

競プロではコードを速く書くために、よく使う関数・マクロ・定数などをプログラムの上部に書いておく人が多いです。
そこで、一例として自分が使っているテンプレートを紹介します。
テンプレートには様々な流派があると思いますが、私は比較的頻繁に使う機能に絞って、コーディング中も画面内に全体が収まるくらいにしています。
(あまりに長いと、あとで提出コードを見返したときに、スクロールするのがめんどくさそうであるため)

まずは一旦全体を載せてから、それぞれ簡単な説明をしていきます。

#if __has_include(<acl_bits.hpp>)
#include <acl_bits.hpp>
#else
#include <bits/stdc++.h>
#include <atcoder/all>
#endif
using namespace atcoder;
using namespace std;
using ll=long long;
using ld=long double;
using P = pair<ll,ll>;
using TUP = tuple<ll,ll,ll>;
#define rep(i,n) for (int i = 0; i < (n); ++i)
template<typename t,typename u>inline bool chmax(t&a,u b){return a<b?a=b,1:0;}
template<typename t,typename u>inline bool chmin(t&a,u b){return a>b?a=b,1:0;}
int yn(bool b){cout<<(b?"Yes\n":"No\n");return 0;}
constexpr int di[]={1,0,-1,0};
constexpr int dj[]={0,1,0,-1};
using mint = modint998244353;
ostream&operator<<(ostream&os,const mint& x){os<<x.val();return os;};
template <typename t>ostream&operator<<(ostream&os,const vector<t>&x){for(const t&i:x)os<<i<<' ';return os;}
template <typename t>ostream&operator<<(ostream&os,const vector<vector<t>>&x){for(const vector<t>&i:x)os<<i<<'\n';return os;}
constexpr ll inf=1LL<<60;

23行です。

#if __has_include(<acl_bits.hpp>)
#include <acl_bits.hpp>
#else
#include <bits/stdc++.h>
#include <atcoder/all>
#endif

bits/stdc++.h、atcoder/allヘッダーのプリコンパイル - 提出時の切り替えで紹介したものです。

using namespace atcoder;
using namespace std;

std::coutcoutと書けるようになったり、atcoder::dsudsuと書けるようになったりします。業務等の大規模なプロジェクトでこれをやってしまうと、名前が衝突する可能性がありますが、競プロでは速度が命です。

using ll=long long;
using ld=long double;
using P = pair<ll,ll>;
using TUP = tuple<ll,ll,ll>;

右辺の型を左辺の名前で使えるようにしています。PTUPが大文字なのは、どちらも変数名用に小文字表記をとっておきたいからです。

#define rep(i,n) for (int i = 0; i < (n); ++i)

APG4bでも紹介されているrepマクロです。単純なループしかしないときに、わざわざfor文を書くのはめんどくさいです。

template<typename t,typename u>inline bool chmax(t&a,u b){return a<b?a=b,1:0;}
template<typename t,typename u>inline bool chmin(t&a,u b){return a>b?a=b,1:0;}

chはchangeの意です。chmaxは$a < b$のときに$a$$b$を代入し、1を返します。$a \ge b$のときは何もせず、0を返します。
つまり、$a$をmaxに更新して、そのとき値が変化したのか?を返します。
chminはminに更新するバージョンです。
DPやダイクストラ法で使います。
また、簡易的に付け加えると、templateは引数の型になんでも取れるようにする機能で、inlineはコード内に関数を展開することで、動作が早くなったりするという機能です。

int yn(bool b){cout<<(b?"Yes\n":"No\n");return 0;}

Yes/Noを簡単に出力します。
yn(a==b)のように、条件文を入れて使うと便利です。
また、return yn(b);のようにしてそのままプログラムを終了することもできます。

constexpr int di[]={1,0,-1,0};
constexpr int dj[]={0,1,0,-1};

グリッドでの幅優先探索をする時に、上下左右の隣接マスの座標を求めるために使います↓

int ci,cj; // 現在の座標
rep(i,4){
    unsinged ni=ci+di[i],nj=cj+dj[i];
    if(ni < h && nj < w)
        //処理
}
using mint = modint998244353;

Atcoder Libraryのmodint$\mod 998244353$で使います。
たぶん水色diff以上でしか使う機会がないです。
$\mod 1000000007$の問題のときは、modint1000000007に書き換えますが、最近のAtcoderは基本的に998244353です。

ostream&operator<<(ostream&os,const mint& x){os<<x.val();return os;};

modintにはoperator<<が定義されておらず(なぜ?)、.val()を読んでintに変換しないと出力できないという仕様で、それはめんどくさいので、自分でoperator<<を定義します。

template <typename t>ostream&operator<<(ostream&os,const vector<t>&x){for(const t&i:x)os<<i<<' ';return os;}
template <typename t>ostream&operator<<(ostream&os,const vector<vector<t>>&x){for(const vector<t>&i:x)os<<i<<'\n';return os;}

vector<t>vector<vector<t>>にもoperator<<を定義し、coutでいい感じに出力してくれるようにします。

constexpr ll inf=1LL<<60;

infです。プログラムで使う変数名とは被らなさそう&大文字打つのめんどくさいということで、小文字表記にしてます。どういう値にするかは様々な流派がありますが、私はinfの値を過信せず、毎回問題ごとにあり得る最大値を考えるのがいいと思い、適当に1LL<<60にしてます。

ブラウザ拡張機能の導入

ブラウザに拡張機能を入れて、コンテストページの操作を快適にしたり、重要な情報を得やすくします。
AtCoderをするとき、入れておくといい拡張機能などなどを参照し、使いたい拡張機能を入れると良いと思います。
また、AtCoder Clansに網羅的にまとめられており、こちらも参考になると思います。
例として私はこんな感じで入れています。
image.png
AtCoder Easy Test v2は、コンテスト中は動作が重くなるので、手元でテストする方が早いです。

もっと快適にしたい場合

  • atcoder-cli・ojを導入して、自動テスト&CLIでの提出をする
  • ターミナルやエディタを半透明にする(ノートPCなどでウィンドウをたくさん開くときに便利です。)
  • スニペットを利用してライブラリを貼り付けられるようにする
  • エディタにこだわる(個人的にはNeovimがおすすめです。)

など

終わりに

結局精進が一番大事です。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?