kit84 talks

社会人 / AtCoder 緑 / くBC 水

HackerRank で問題文の見映えを良くする一般的なテク

はじめに

こんにちは。kit84 (AtCoder 緑) です。
最近、競技プログラミングを始めたのですが、いろいろと学びがあって楽しいですね。

私も何か競プロ記事を書いてみようと思い立ち、遂にブログを生やしました。
生暖かい目でご覧ください。


地の文

数式交じりの文の解説記事では、「数式」にフォーカスが当てられがちで、文章自体のフォーマットに関する記事はほとんど見ません。

競プロの問題文を読みやすいものにするために、役に立ちそうな Tips をまとめておきます。

段落内改行は行末にスペース 2 つ

競プロの問題文は、頻繁に改行したくなるものです。 でも、普通に改行しても表示上は無視され、改行になりません。 だからといって空行を入れると、段落分けになってしまいます。

どうすればよいでしょうか? 答えは、「行末にスペースを 2 つ置く」です。
(マークダウンの仕様です。)なぜ誰も教えてくれなかったのか……

input

> この行の末尾に空白なし。
> この行の末尾に空白あり。  
> あかさたなはまやらわ。

output

この行の末尾に空白なし。 この行の末尾に空白あり。
あかさたなはまやらわ。

note
行末のスペースが 1 つ以下の場合は、ホワイトスペースが無視されます。
HackerRank の場合、引用文の内側では段落分けができないようです。(?)

文中数式の埋め込みは \\( \\)

文中のあらゆる数式、変数名、数字は \\( \\) で囲みます。
HackerRank の場合、バックスラッシュは 2 個必要です。代わりにドル記号 $ $ でもよいです。
独立行形式の数式は、\\[ \\], $$ $$ です。

数字や数式の前と後ろにはスペースを入れましょう。(くっつけると表示が変です。)

input

> \\(H\\)行\\(W\\)列の格子が\\(1\\)個ある。  
> \\(H\\) 行 \\(W\\) 列の格子が \\(1\\) 個ある。

output

\(H\)行\(W\)列の格子が\(1\)個ある。
\(H\) 行 \(W\) 列の格子が \(1\) 個ある。

note
スペースを入れない場合、かなり詰まって見えてしまいます。
余裕を持たせたほうが読みやすく、見映えがよいです。

入力形式は引用文で

引用文 (> ほげ) を使ってあげると背景色がつくので見やすいです。(あくまで HackerRank 環境の場合)
一方、コード片 (`ほげ`) を使うと MathJax が効きません。

input

> \\(N\ \ D\\)  
> \\(A_{1,1}\ \ A_{1,2}\ \ \cdots \ \ A_{1,D}\\)  
> \\(\ \\, \vdots\\)  
> \\(A_{N,1}\ \ A_{N,2}\ \ \cdots \ \ A_{N,D}\\)

output

\(N\ \ D\)
\(A_{1,1}\ \ A_{1,2}\ \ \cdots \ \ A_{1,D}\)
\(\ \, \vdots\)
\(A_{N,1}\ \ A_{N,2}\ \ \cdots \ \ A_{N,D}\)

note
1 行ごとに数式を分割すると楽です。
改行が必要な場合は、行末(数式の外)にスペースを 2 個置きます。
隠し味として、\vdots の手前にも適度に空白を入れておくと見映えがいいです。

しかし、 \, (微小な空白)が壊れることがありますね。\\, にすると大丈夫だったりします。(HackerRank は何かと罠があって、試行錯誤が要求される)

制約は箇条書きで

箇条書きは、行頭を - にします。

input

> - 入力はすべて整数
> - \\(1\le N\le 10^5\\)
> - \\(1\le D\le 8\\)
> - \\(0\le A_{i,j} \le 10^9 \quad\\) \\((1\le i\le N,\,\ 1\le j\le D)\\)

output

  • 入力はすべて整数
  • \(1\le N\le 10^5\)
  • \(1\le D\le 8\)
  • \(0\le A_{i,j} \le 10^9 \quad\) \((1\le i\le N,\,\ 1\le j\le D)\)

note
(別に引用文にする必要はないのですが、引用文の中でも箇条書きができます。)
添字の範囲を書くカッコの手前には、\quad で空白を入れましょう。
また、カンマで数式を区切るときは、カンマの後に空白を入れると見やすいです。
数式が横に長くなると、モバイル環境などで画面からハミ出すことがあるので、適度に分割すると良い感じに改行してくれます。

画像

画像ボタンを押して、画像を貼ります。

input

![image](https://s3.amazonaws.com/hr-assets/0/1576162900-e685ba7f3a-E26E96F9-1DDE-4F93-B0EF-03C168B729DD.png)

output
image

note
画像が大きすぎましたね。
この画像は何かというと、水筒を傾けるやつ の反省画像です。


数式

数式で「出したい記号があるけど出し方が分からない!」という場合は、検索すれば分かるので(→ 参考サイト)いいとしても、「なんとなく書ける間違った書き方」という厄介パターンが割とあります。
ここでは、競プロで使いがちな例をピックアップして紹介します。

乗算記号は \times

プログラマは乗算の意味で \(*\) を使うことに抵抗はないと思うのですが、本来は乗算の記号ではないです。(畳み込みとかの意味で使うことがある)

input output note
a*b \(a*b\) 好ましくない (文脈によると思います)
a\times b \(a\times b\) 乗算、外積
a\cdot b \(a\cdot b\) 乗算、内積
ab \(ab\) だいたい省略しますよね

関係・比較演算子

\ll( \(\ll\) )とか \gg( \(\gg\) )まで覚えておくと、かっこいい文章が書けます。
\leqq( \(\leqq\) )とかを使うと横棒 2 本になりますが、フォント的にあまり美しくありません。
\leqslant( \(\leqslant\) )も… 私は好きですが、全然見かけませんね。

input output note
a<b \(a\lt b\) \lt も同じ
a>b \(a\gt b\) \gt も同じ
a\le b \(a\le b\) \leq も同じ
a\ge b \(a\ge b\) \geq も同じ
a\ll b \(a\ll b\) a<<b(\(a<<b\))はダメ
a\gg b \(a\gg b\) a>>b(\(a>>b\))はダメ
a=b \(a=b\) 等号
a\ne b \(a\ne b\) \neq も同じ
a\not\equiv b \(a\not\equiv b\) 否定は \not と組み合わせる
a\fallingdotseq b \(a\fallingdotseq b\) 日本人がよく使う
a\approx b \(a\approx b\) 海外でも使う

関数としての文字

\log, \max みたいなやつ。\ をつけるだけ。
これは、最低限のルールです。 \ をつけているかどうかで、その文章のレベル感がバレてしまいます。

input output note
O(N log max A) \(O(N log max A)\) いやなきもち (全文字の積になる)
O(N\log\max A) \(O(N\log\max A)\) よさそう
\tan^{-1}x \(\tan^{-1}x\) アークタンジェント
\arctan x  \arctan x% 〃(あるものは使おう)
\mathop{\rm atan}x \(\mathop{\rm atan}x\) 〃(無いものは作ろう)
a\mathbin{\rm xor}b \(a\mathbin{\rm xor}b\) 排他的論理和二項演算子として)

二項係数

nCk、入力が面倒なことであまりにも有名。
一方、\binom NK だと \(\binom NK\) になってしまい、中学・高校では馴染みがない感じです。

input

\\[
    \def\nCk#1#2{ {}_ {#1}\mathrm{C}_ {#2} }
    \nCk{N}{K}
\\]

output

\[ \def\nCk#1#2{ {}_ {#1}\mathrm{C}_ {#2} } \nCk{N}{K} \]

note
上記のようにマクロを 1 回宣言しておけば、以降、同一ファイル内では \nCk ab ( \(\nCk ab\) ) のように繰り返し用いることができます。

ここで、アンダーバー _ が 2 回出現した場合の挙動がおかしい(マークダウンの下線機能と衝突する)ので、あとに空白を入れて回避しています。

切り上げ・切り捨て

ガウス記号 [ ] を floor 関数の意味で使うのは、混乱を招くのでよくないです。

\left\lfloor, \right\rfloor みたいなやつを使います。
ついでに絶対値も。

input output note
\lceil \cfrac ab \rceil \(\lceil \cfrac ab \rceil\) はみだしちゃう
\left\lceil \cfrac ab \right\rceil \(\left\lceil \cfrac ab \right\rceil\) 切り上げ
\left\lfloor \cfrac ab \right\rfloor \(\left\lfloor \cfrac ab \right\rfloor\) 切り捨て
\left| \cfrac ab \right| \(\left\vert \cfrac ab \right\vert\) 絶対値

縦棒 |\vert と同じです。

縦棒が出てきたので。
「 \(a\) は \(b\) を割り切ります」ということを、記号 a\mid b で表すことがあります。(→ 参考

input output note
a|b \(a\vert b\) にてるけどちがうよー
a\not| b \(a\not\vert b\) にてるけどちがうよー
a\mid b \(a\mid b\) 割り切る
a\not\mid b \(a\not\mid b\) 割り切らない
a\nmid b \(a\nmid b\) 割り切らない

なお、私は使ったことがありません。(どっちが割る方か忘れてしまうため)

剰余

よくある mod ですが、なぜか 3 種類も用意されています。
間隔が空かない \bmod は、二項演算子としての使用を意図しています。

input output note
a=p mod 10^9+7 \(a=p mod 10^9+7\) これはいけない
a \equiv p \mod 10^9+7 \(a\equiv p\mod 10^9+7\) 合同式用の間隔が空く
a \equiv p \pmod{10^9+7} \(a\equiv p\pmod{10^9+7}\) 括弧付き
a \equiv p \bmod 10^9+7 \(a\equiv p\bmod 10^9+7\) 間隔が空かないのでダメ

一般記号の \(\%\) を二項演算子として使いたい場合は、 \mathbin{\%} とすると良いでしょう。

input output note
a\mod b \(a\mod b\) これは片寄るのでダメよ
a\bmod b \(a\bmod b\) 剰余をとる演算
a\% b \(a\% b\) % の前には \ が必要
a\mathbin\% b \(a\mathbin\% b\) 二項演算子用の間隔が空く

大きい数は {,} で桁区切りします。普通にカンマを使うと、離れすぎてしまいます。

input output note
998244353 \(998244353\) よく見る素数
998,244,353 \(998,244,353\) 998 と 244 と 353 という 3 つの数
998{,}244{,}353 \(998{,}244{,}353\) カンマが通常記号として働く
998\,244\,353 \(998\,244\,353\) 微小な空白でもよい

場合分け

場合分け環境を使います。
HackerRank の場合、数式中の強制改行は \\\\ になります。 \cr としても同じです。

input

\\[\max(a,b) = \begin{cases}
    a & (a \ge b \ \text{のとき}) \\\\
    b & (a \lt b \ \text{のとき})
\end{cases}\\]

output

\[\max(a,b) = \begin{cases} a & (a \ge b \ \text{のとき}) \cr b & (a \lt b \ \text{のとき}) \end{cases}\]

note
ところで、「の」だけフォントがおかしくなりませんか? MathJax の仕様らしいです。(→ 「の」の謎
(表示は環境によります)

input output note
\\((x のとき)\\) \((x のとき)\) おかしい「の」
( \\(x\\) のとき) ( \(x\) のとき) ふつうの「の」

数式中に日本語を置くのは、避けた方がいいかもしれません。

空白はガンガン調整してよい

ここからは です。
ですが、実はここからが本題です。

空白は、 \rm \TeX% が自動で良い感じに調整してくれるものだと思っていた時期が、私にもありました…

しかし、そうとは限りません。むしろ空白調整するほうが推奨される場合もあります。
たとえば、カンマが入っている式では、空白を明示的に入れないと解釈が変わってしまいます。

input output note
a+b, x+y \(a+b, x+y\) (a) と (b,x) と (y) の和
a+b,\ x+y \(a+b,\ x+y\) (a+b) と (x+y)

上記では、\ (バックスラッシュ + スペース)を使って強制的に空白を入れています。

調整するための道具は他にもあります。下記の通りです。
(ここで、em は字幅の単位で、大文字 M の字幅を表します)
\ だけは実験的に求めた値なのですが、四分アキに相当するものに見えています)

input output note
MN \(MN\) 並べた文字は積として解釈される
M\!N \(M\!N\) 負のスペース(-3/18 em)
M\!\!N \(M\!\!N\) すごい負(-6/18 em)
M\!\!\!N \(M\!\!\!N\) やばい(-9/18 em)
M N \(M N\) ホワイトスペースは無視される(くっつけて書くのと同じ)
M\:\!N \(M\:\!N\) なにこれ?(1/18 em)
M\;\!N \(M\;\!N\) 意味あるの?(2/18 em)
M\,N \(M\,N\) 微小なスペース(3/18 em)
M\:N \(M\:N\) 小さめのスペース(4/18 em)
M\;N \(M\;N\) やや小さいスペース(5/18 em)
M\ N \(M\ N\) 普通の空白(4.5/18 em)
M\enspace N \(M\enspace N\) \quad の半分(9/18 em)
M\quad N \(M\quad N\) 大きいスペース(18/18 em)
M\qquad N \(M\qquad N\) かなり大きいスペース(36/18 em)
M\hspace{12px}N \(M\hspace{12px}N\) 長さ指定スペース

ええ… 全部いっしょじゃないですか 具体例で見ていきましょう。
…と思いましたが、長くなりそうなので続きは次の機会にします。

興味がある場合は、読み物として 「Takatalab - 正しいマークアップ」をおすすめします。


おわりに

競プロの問題文を打ち込んでいて「なんか読みづらいな」と思うときは、コンテスト参加者にとっても読みづらいものです。
ここら辺、ちょっと意識できるようになると、問題文のクォリティが数段上がります。
私は、大げさな話ではないと思っています。


文献