Page List

Search on the blog

2011年4月17日日曜日

SRM 503(div2) 参戦記

[結果]
Easy : やるだけ。
Middle: 頭の柔らかさが必要な問題。greedyかDPだろって思いこんで突き進んだ結果解けず。
Hard : MST。やるだけ。本番は開かず。。。

[反省]
  1. 固定概念は捨てる。greedyかDPだろって思いこんだのが敗因。
  2. 全体像を考える前に、まず例外パターンは捨てる。middleでいきなり汎用的な解法を考えようとしたが、例外パターン(-1となるパターン)を始めに切り捨てておけば、それ以外の答えが{1, 2}のどちらかしかないというのに気付けたはず。プログラムで深いネストをしないためには、例外を一番最初にcontinueすればいいが、同じようにアルゴリズムを考える上でも例外は最初に排除すべき。例外を排除することで見えてくる規則性があるのだから。
  3. 次回からはHardも開くようにしよう。今回みたいに簡単な問題もあるので。。
[Rating]
1088 -> 1061
次回こそdiv1に返り咲く!うまく行かなかった回からは学ぶべきものが多いはずなので、しっかり分析して次に活かす。後はひたすら練習。

2011年4月14日木曜日

Haskell勉強記(3)

またまたHaskell。書きすぎ!?

今日は、
  1. function composition
  2. where clause
  3. cons
について。

 function compositionは、その名のとおり合成関数です。f, g, hという関数がある場合、
    f (g(h(x)))
は、Haskellでは、
    f $ g $ h x
と書けます。また、function compositionを使用すれば、
(f . g . h ) x
と書けます。使いどころとしては、mapの第一引数に合成関数を使用したい場合でしょう。

 次にwhere 節について。where節は、関数の中で有効なローカル変数、関数を定義します。

以上をふまえて、まず、sin (x^2)を計算するプログラムを書きます。
main = print $ map (sin . sq) [1..10]
where sq x = x * x
まあ、そのままですね。。

最後に、consについて。consは":"(コロン)のことです。パターンマッチの説明のときに出てきました。
a : x
とすると、要素aをリストxの先頭に追加することができます。
例えば、リスト[1,2,3,4,5]は、
 1:2:3:4:5:[]
と書けるわけです。(最後に[]が必要なことに注意!)

では、最後に自前のzip関数を定義してみます。奇数リストと偶数リストを生成してzipしてみましょう。
main = print $ myZip [1, 3 .. 10] [2,4 .. 10]
where myZip [] [] = []
myZip (x:xs) (y:ys) = (x,y) : myZip xs ys
今日はここまで。

2011年4月13日水曜日

Haskell勉強記(2)

今日もHaskell。

今日は、昨日の復習とlambda expressionについて。
まず、昨日の復習として、
  • map
  • filter
  • reduce
を自分で実装してみます。これを実現するためには、
  1. Pattern match
  2. tuple
を使えばいいです。
myReduce     f [] = 0
myReduce f (x:xs) = f x (myReduce f xs)

myMap f [] = []
myMap f (x:xs) = [f x] ++ (myMap f xs)

myFilter f [] = []
myFilter f (x:xs) = if f x
then [x] ++ (myFilter f xs)
else myFilter f xs
まぁこんな感じでしょう。

そして、上記自前関数の引数にlambda式を使ってみましょう。
Haskellでは、lambda expressionは"\"(バックスラッシュ)と"->"(ハイフンと大なり記号)を用いて表されます。
a = [1 .. 10]
main = do print $ myMap (\x -> x*x) a
print $ myFilter (\x -> x `mod` 2 /= 0) a
print $ myReduce (\x y -> x+y) a

なんか、意味不明な記号が多くて若干pe○lみたいで嫌ですが。。
今日はここまで。

2011年4月12日火曜日

Haskell勉強記(1)

最近Haskellを勉強しています。
これからこまめにHaskellを勉強して、学んだ内容を公開していきます。

今日の内容は、
  1. pattern match
  2. list match
  3. concatenation
です。

例題として、quick sortを書いてみます。
a = [31, 10, 19, 90, 100, 1, 3, 4]
qsort [] = []
qsort (x:xs) = qsort (filter (<x) xs) ++ [x] ++ qsort (filter (>=x) xs)
main = print $ qsort a
qsortの定義が2つありますが、これはpattern matchと呼ばれています。
C++では、引数の型や型の数によって関数の挙動を変えることができます。(overload)
Haskellの場合は、引数の値に応じて関数の戻り値を変えることができます。

次に、(x: xs)という表現に注目。
Haskellでは、リストを先頭部分とそれ以外に分解して表現することができます。
xがリストの先頭要素、xsはそれ以外です。
これも、一種のpattern matchと考えることができます。

filterに関しては、一般的な関数言語の機能なので割愛。

最後に++演算について。
これは、concatenationです。リストを連結します。

Haskellだと、quick sortがかなりシンプルに書けますね!
今日はここまで。

2011年4月11日月曜日

バグを出さないプログラムテクニック(1)

よくあるバグの代表例は、
  • 無限ループ
  • out of bounds 系エラー
あたりだろう。
これらのバグは、ちょっとした事を実践することで回避することができる。

それは、whileループの禁止である。
whileループを使うと、
  • やたらと処理が複雑になったり、
  • 条件式が偽にならず無限ループになったり、
と悪いことばかり。
大抵のwhileループは、forループで書けるので、whileは禁止してしまってもいいかと思う。
(あと余談だが、ショートコーダーはwhileループは使わないらしい。)

例えば、この問題。

for文で書くと、綺麗に書けます。
whileで書くと、多分カオスになるでしょう。そしてバグが出るはず・・。

for文で書いたプログラムはこちら。
vector<pair<int, int> >vec, ret;
int main() {
int n, s, t;

scanf("%d", &n);
while (n--) {
scanf("%d %d", &s, &t);
vec.PB(MP(s,t));
}

sort(ALL(vec));
int pos = 0;
for (;pos < (int)vec.size(); pos++) {
int s = vec[pos].first;
int t = vec[pos].second;

for (; pos+1 < (int)vec.size() && vec[pos+1].first <= t; pos++)
t = max(t, vec[pos+1].second);

ret.PB(MP(s, t));
}

REP(i, ret.size())
printf("%d %d\n", ret[i].first, ret[i].second);

return 0;
}


2011年4月10日日曜日

Being hooked on something makes you strong

I watched a TV drama entitled "aibou," which means buddy, co-worker or that kind of thing in Japanese, on this new year's day.
On the drama, a wife who had lost her son by a traffic accident, made explosive bombs and revenged on someone who killed her son.
On the drama, a detective, who solved the case, said "You think that an average house wife cannot make bombs, right? But one can do anything if s/he tries it all out."
And he continued, "She became really keen on studying about bomb. It made her proactive, outgoing and lively. Even though she stayed up late in the night -- 2 or 3 AM -- every day, she never looked sleepy. She looked cheerful every day."

あれ、何か。前置きが長くなった。
まー要約すると、最近仕事が忙しいことを言い訳にしてアルゴリズムの勉強をサボりがちだったけど、それは違うなと思いました。

本当に好きなこと、本当にやるべきことであれば、寝る時間を削ってでもやるべきだと。
そして、その方が活力的な生活が送れるはずだと信じています。

って、こんな精神論を書きたいわけじゃなかったのですが、
今日は、中国語の問題を解きました。


ちょっと文章が分かり難いですが、要は、
「mをn個以下に分割するパターンを求めよ。」
です。

分割数は、動的計画で解くのが定石。
分割数を知らない人はこのページを見よう!!

これくらいの規模ならDFSとかで解けそうですが、入力サイズが大きくなると動的計画じゃないと厳しそうです。。
int dp[16][16];

void init() {
REP(i, 16)
dp[i][0] = 0, dp[0][i] = 1;
dp[0][0] = 1;

FOR (i, 1, 16) FOR (j, 1, 16) {
if (i-j >= 0)
dp[i][j] = dp[i][j-1] + dp[i-j][j];
else
dp[i][j] = dp[i][j-1];
}
}

int main() {
int t, n, m;

init();
scanf("%d", &t);
REP(i, t) {
scanf("%d %d", &n, &m);
printf("%d\n", dp[n][m]);
}

return 0;
}

2011年4月6日水曜日

カントール集合

昨日、初めてフラクタルをプログラムで描画した。
実は、フラクタルは結構難しい。二次元の場合は正確かつ高度な実装能力が必要だ。

ただし、カントール集合は一次元なので簡単。

始点、終点を引数にして再帰関数で処理をすればOK。

カントール集合は、不可算集合の代表的な例らしい。
非加算集合とはその名の通り、『その要素に番号を割り振ることのできない集合』である。
なんじゃそりゃ!?って感じですね。
簡単に言うと、無限個の整数を使っても番号を割り振ることのできないくらい、濃度の濃い集合ということです。例えば、実数の集合も非加算集合です。

カントールさんは、この非加算集合の存在を数学的に示したそうです。
wikipedia(英語版)に、とても面白い内容がありました。

Cantor's diagonal argument(カントールの対角線論法)です。

要約すると、以下のような感じ。

0または、1のみから構成される無限長リストの集合を考える。
この無限長リストに番号をつけて、適当な要素列s1、s2、...を考える。

s1 = (0, 0, 0, 0, 0, 0, 0, ...)
s2 = (1, 1, 1, 1, 1, 1, 1, ...)
s3 = (0, 1, 0, 1, 0, 1, 0, ...)
s4 = (1, 0, 1, 0, 1, 0, 1, ...)
s5 = (1, 1, 0, 1, 0, 1, 1, ...)
s6 = (0, 0, 1, 1, 0, 1, 1, ...)
s7 = (1, 0, 0, 0, 1, 0, 0, ...)
   ...
とすると、対角成分(左上から右下にかけて)に移動しながら、通過した数字と異なる数字を選ぶ。
  s0 = (1, 0, 1, 1, 1, 0, 1, ...)

すると、このs0は、どのsiとも異なる。
ここで面白いことに、
  • s0は集合sに含まれる。(1, 0からなるリストなので)
  • 同時に、s0は集合sに含まれれない。(上記のような操作をすれば、集合に含まれないようなリストを作成できる。)
の両方が成立する。
これは矛盾なので、
この無限長リストに番号をつけて、適当な要素列s1、s2、...を考える。
という操作は不可能だということが分かる。

よって、集合ss1s2s3s4は数えることはできない。

どういう頭の構造をしてたら、こんなこと考えつくんでしょうね・・。