SRM: tomerun
Marathon: tomerun
Codeforces:tomerun
小さな会社で何でも屋な感じのプログラマやってます
Java・C++・C#・Ruby・Objective-C
※ICPC参加経験はありません
紹介するのは、コンテストのコードを書くために特化したスタイルです。
仕事や研究で使うような、長期的にメンテナンスする可能性のあるコードで使うのは推奨されないものも多くあります(推奨できるものもあります)。
よくある問題:「〜の組み合わせ数を 1,000,000,007 で割った余りを求めよ」
よくあるコード:
const int MOD = 1000000007; // 桁数あってる?
…
dp[i] %= MOD;
ぱっと見で桁数がわかるように書くと安心:
const int MOD = 1000 * 1000 * 1000 + 7;
または
const int MOD = (int)1e9 + 7;
コピペできる場合はそれで良いわけですが…
たとえば、int同士の割り算の結果をdoubleで欲しい場合
int distance,speed;
…
double time = (double)distance / speed;
キャストの構文を書くよりも、1.0を掛けてdoubleへ変換した方がタイプ量が少なくて済む
double time = 1.0 * distance / speed;
さらに1文字少なくできる
double time = 1. * distance / speed;
よくある二次元グリッドでの探索問題:次のようなH*Wのセル内でスタートからゴールまでうんたらかんたら
..X. .XS. G..X
よくあるコード:
int DX[4] = {1, 0, -1, 0}; int DY[4] = {0, 1, 0, -1};
…
for (int k = 0; k < 4; ++k) {
int nextX = x + DX[k];
int nextY = y + DY[k];
if (nextX < 0 || W <= nextX || nextY < 0 || H <= nextY) continue;
…
}
(nextX < 0 || W <= nextX || nextY < 0 || H <= nextY)
とか超めんどい
次のように、自分で周囲を壁で覆ってやれば境界外へはみ出ることのチェックは不要になる
XXXXXX X..X.X X.XS.X XG..XX XXXXXX
座標が1つずれることに注意
問題「与えられた文字列の各文字が母音だったらうんたらかんたら」
まず思いつくコード:
for (int i = 0; i < str.size(); ++i) {
if (str[i] == 'a' || str[i] == 'i' || str[i] == 'u'
|| str[i] == 'e' || str[i] == 'o') {
…
}
}
手でたくさん並べて書きたくない。コピペしてるうちにミスりそうだし
プログラムにやらせよう
for (int i = 0; i < str.size(); ++i) {
if (string("aiueo").find(str[i]) != string::npos) {
…
}
}
これ以外にも、何かの数を数えてテーブルを生成するような場合は、手で数えた結果を埋め込むよりプログラムにやらせた方がミスしにくい
文字列処理をやる問題で、始点と終点が1ずれてバグることがよくある→紙に書きましょう
気をつけること:
ユークリッド距離はもちろん sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))
ですが
処理の途中ではsqrtをせずに距離の2乗で扱えば良いケースも多い。最大の距離のものを調べる場合とか。
速度が速くなるのと、処理途中での誤差を気にしなくてよくなるのと
スローガン「除算より乗算、べき根よりべき乗」
もう1つの例:整数の約数を調べるときなんかに「sqrt(N)まで調べれば良い」ということで次のようなコードを書くが
for (int i = 2; i <= sqrt(N); ++i) {
…
}
ルートより2乗にした方が安心できる
for (int i = 2; i*i <= N; ++i) {
…
}
(これはこれでオーバーフローの可能性があるのでどっちもどっちかな…)
やろうとしていること:「配列の各要素について、自分よりも前に自分と等価な要素があったら何かする」
for (int i = 0; i < N; ++i) {
bool found = false;
for (int j = 0; j < i; ++j) {
if (a[i] == a[j]) {
found = true;
break;
}
}
if (found) {
…
}
}
この例はSTL使ったらもっと簡単に書けますが、いったんそれは置いておいて。
問題点:ネストが深くて読みにくい・foundという本質的じゃなさそうな変数が出現する
→関数化しましょう
bool any(int s, int e, int v) {
for (int i = s; i < e; ++i) {
if (a[i] == v) return true;
}
return false;
}
for (int i = 0; i < N; ++i) {
if (any(0, i, a[i])) {
…
}
}
C++の配列は、ローカル変数の場合には最初ゴミの値が入っているので自分で初期化しないといけません。
memsetでも良いですが
int a[1000];
memset(a, 0, sizeof(a));
空の初期化子を指定するだけで0埋めされます
int a[1000] = {};
配列じゃなくて構造体でも同じように0初期化できます
問題:整数の配列が与えられる。値が大きい順にそのインデックスを出力せよ。
C++の場合は配列の値とインデックスをpairに入れてソート、で済みますが
Javaだと標準でPairが無いので自分で比較用のクラスを作らないといけない。面倒
次のように、1つの整数の中の上下ビットに分けて埋め込めばクラス作らなくても良い
int a = new int[N];
for (int i = 0; i < N; ++i) {
a[i] = (v[i] << 16) + i; // オーバーフロー注意
}
Arrays.sort(a);
java.lang.Integerクラスあたりに、ビット演算に便利な関数があります。把握しておきましょう。
わざわざ自分で書かなくて済むよう。
Integer.bitCount(0x137F); // => 10
Integer.highestOneBit(0x137F); // => 0x1000
Integer.numberOfTrailingZeros(8); // => 3
あとjava.util.BitSetクラスも地味に便利(けど自分以外で使われてるのをあまり見たことがない…)