Page List

Search on the blog

2011年9月14日水曜日

蛙逢引 ~一次不定式で愛を探す~

蛙が逢引するロマンティックな問題。この問題は、問題文的にも、内容的にも好きだ。


ここに訳してくれている人がいる。http://d.hatena.ne.jp/kabus/20060728

以下問題の解き方。

x + mt = y + nt (mod L)より、
x + mt = y + nt + kL
(n-m) t + Lk = x - y となるような整数(t, k)の組を見つければいい。(問題では、最小の正の整数t*を求めることになるが、これは求まったtに対する代表元を考えればいい。)

このような解が存在する必要十分条件は、(x-y)がgcd(n-m, L)で割り切れることである。

まず、必要条件の証明。
もし、(x-y)がgcd(n-m, L)で割り切れない場合に、(t, k)が存在したとする。
このとき、
左辺 ≡ 0 (mod gcd(n-m, L))に対して、右辺 > 0 (mod gcd(n-m, L))となり矛盾。よって(x-y)がgcd(n-m, L)で割り切れない場合は、(t, k)の組は存在しない。

次に、十分条件の証明。
両辺をgcd(n-m, L)で割る。
(n-m)/gcd(n-m, L) をA、L/gcd(n-m, L)をB、(x-y)/gcd(n-m, L)をCとおくと、
At + Bk = Cと書ける。このとき、AとBは互いに素なので、Aを法にしたときのBの逆元が存在する。
つまり、y ≡ 1/B (mod A)となるようなyが存在する。
よって、1 - By ≡ 0 (mod A)。これは、(1 - By)がAで割り切れることを意味しているので、
x = (1 - By) / Aとなる整数xが存在する。これを変形すると、Ax + By = 1となり、この式を満たす整数(x, y)が存在する。これを両辺C倍してやると、A(Cx) + B(Cy) = Cとなり、(Cx, Cy)が(t, k)に他ならない。

ということで、解が存在するか否かは、gcdを出すだけで良いです。加えて、(n-m) t + Lk = x - y を満たす(t, k)の組は、(n-m)t' + Lk' = gcd(n-m, L)を満たす(t', k')を拡張ユークリッド互除法で出し、gcd(n-m, L)と(x - y)の比に合わせてスケーリングしなおせばOK。

最後に、問題となるのは、求まったtに対して、それと合同な最小の正の整数t*をどう出すか?そもそも何を法にして考えるか?これは、目盛りがL個付いている時計を(n-m)目盛りずつジャンプしていくと、何回ジャンプすると、同じ場所に戻ってくるかを考えればいいです。L/gcd(L, n-m)が答えです。ということで、tの法(L/gcd(L, n-m))における代表元を出せばOK。

以下回答ソース。

LL extgcd(LL a, LL b, LL &x, LL &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }

    int d = extgcd(b, a%b, y, x);
    y -= (a/b) * x;
    return d;
}

LL solve(LL x, LL y, LL m, LL n, LL L) {
    if (m == n)
        return -1;

    LL k, t;
    LL g = extgcd(n-m, L, k, t);
    if ((x-y) % g)
        return -1;

    LL ans = (x - y) / g * k;
    L /= g;

    return (ans % L + L) % L;
}

int main() {
    LL x, y, m, n, L;

    cin >> x >> y >> m >> n >> L;

    LL ret = solve(x, y, m, n, L);
    if (ret < 0)
        puts("Impossible");
    else
        printf("%I64d\n", ret);

    return 0;
}

2011年9月10日土曜日

PKU Candy Distribution

This is about number theory and I like these kinds of problems:

Since in the problem, N is quite large, you need some analysis to solve it in a given time.

The first observation is how the number would increment if you thought it in the linear system, not in the circular system.

0, 1, 3, 6, 10, 15, 21, 28, ....

It's obvious that the sequence above is sums of an arithmetic sequence, more specifically you'll notice that p(i) = i * (i+1) / 2.

The second observation is its cycle. In modular arithmetic, there's almost always some cycle. Notice that the differences of the sequence above is 1, 2, 3,...., so the minimum possible cycle, --what we are discussing here is the cycle of jumping--, is n, since 0, 1, 2,...n-1 is equivalent to n, n+1, n+2, ... , 2n-1 in the modulo n.

Now the hardest part -- at least in my case--. The cycle of jumping is n as I explain above. So what's the cycle of position. The cycle of this system is the gcd of these two cycles.
Let's consider it by separating two situations: when n is odd, and n is even.

When n is odd, p(n) = n * (n+1) / 2. Since (n+1) is dividable by two, p(n) = 0 (mod n).
While when n is even, p(n) = n * (n+1)/2 = n/2 * n + n/2 = n/2 (mod n).
So the cycle of position is n and 2*n, for an odd number n and an odd number n, respectively.

So it's enough to think just 2*n moves, but it's still a big number -- the biggest possible number is 10^9--, and more analysis is required.

Let's consider when n is odd. p(0) = 0, and p(n-1) = (n-1) * n/2 = 0 (mod n). So for i in {0, 1, 2, ..., n-1}, two number, 0 and n-1, have the same value when mapped onto p. It's impossible to cover all numbers on p with {0, 1, ... n-1}. And since the cycle for odd numbers is n, we can say that the answer for test cases where n is an odd number is "No".

How about when n is even? As discussed in the previous paragraph, p(n) = n/2 (mod n). So for i in {n, n+1, n+2, ....., 2*n-1}, p(i) covers the opposite side of number set against {p(i) | i in {0, 1, ...n-1} }. Therefore it's possible to cover all the numbers if p(n/2) covers all the numbers.

We come to a conclusion that the answer ret(n) is:
"Yes" when n is 1,
"No" whe n is an odd number,
ret(n/2) when n is an even number.

Which comes down to the fact the answer is "Yes" if and only if n is two to the power of i, i = 0, 1, 2,.... . The implementation is pretty easy. Just count the bit set to 1. You can use __builtin_popcount() or n & (n-1) (*).


(*) n & (n-1) sets the LSB of n to zero. In this manner, n & (n-1) is zero iff n is two to the power of i.

Javaで簡易チャットを作る

Javaのソケット通信で遊んでみた。
以前Cでソケット通信をやってみたけど、手続きがややこしくて挫折してしまった。

Javaの場合は、Cと比べ簡単にソケット通信ができる。
ということで、簡易チャットを作って遊んでみた。

ソケットの読み/書きを、SocketReader、SocketWriterというクラスで実装した。チャットの読み/書きは非同期に独立して行われるべきなので、この2つのクラスはスレッドにした。

ソースはこちら。
実行ファイルはこちら。

ネットワークプログラミングはあまりやったことがなかったけど、とても楽しかった。上のプログラムは、いろいろな応用テーマが考えられる。
  1. ソケットの暗号化 シーザー符号とかでもいいけど、フェルマーの小定理を用いた暗号化がおもしろそう。 具体的には、両者間で素数pとべき数pwを決めておく。送信者側で、f : x -> x^pw (mod p)の変換をしてデータを送り、受信者側では、f : x -> x^(p-pw) (mod p)でデータを復元する。あとは、RSAとかを自分で実装してみるとか。(※)
  2. リモートマシンの制御 ソケットでコマンドを送信してあげて、受信側で別プロセスを立ち上げてコマンドを実行する。コマンドの結果をソケットに流すようにすると、別のマシンを制御することができる。これは、自分のサーバーとローカルマシンで試したみたんだけど、いろいろと危ない。
  3. NAT超え 通信を行うコンピュータが両方ともグローバルIPを持っていれば問題なく通信ができるが、そうでない場合は、ルータでポートフォワーディングの設定が必要となる。ポートフォワーディングの設定をしたとしても、ルータ側のIPを知っている必要がある。仲介サーバとかをつかえば出来そうだけど、勉強中。
(※ちょっと勉強したので追記)
RSAは、フェルマーの小定理というよりそれを一般化したオイラーの定理を利用している。上の自分が考えたやり方だと、公開鍵方式にはならない。。
合成数(x = p1 * p2)に対してオイラー関数(totient functionのこと)が、(p1-1) * (p2-1)となるのが嬉しい。p1とp2が分からないと、a^pw = a (mod x)となるpwが分からないってところがポイント。

2011年9月6日火曜日

C++のConcept Checkについて

STLのソースを読んでいて、Concept Checkという概念について知った。
C++でジェネリックプログラミングをするときは、templateを使って引数の型に依存しない関数を定義する。この際、templateとして使用できる型が満たすべき条件を"concept"と呼ぶ。C++には、明示的なconcept checkの機能がないため、template関数に予期しない型を使用した場合、コンパイルが吐き出すエラーが意味をなさないものになりがちである。

例えば、以下の例を考える。

int main()
{
vector<std::complex<float> > v;
stable_sort(v.begin(), v.end());
}

上のソースをコンパイルすると、長くて意味不明なエラーが出力される。(参考ページ1.参照)

これには、ちょっとびっくりさせられる。
エラーが発生した理由は、complexが演算子<をサポートしていないからである。しかし、templateとは単なるplaceholderにすぎないので、stable_sort()の引数であるiteratorが満たすべきconcept "LessThanComparable"をチェックすることができない。結果、深いところまで潜っていって矛盾がでたところでよく分からないあまり意味のないエラーが大量に吐き出される。

これを解決するには、コンパイル時にconcept checkを有効にしてあげるといい。

g++ -o hogehoge -D_GLIBCXX_CONCEPT_CHECKS hogehoge.cpp

-Dxxxxxとは、マクロxxxxを定義してプリプロセッサを実行するという意味。concept checkを有効にすると、LessThanComparableに関する内容がエラーに出力される。

参考ページ:

便利なマクロの使い方

STLライブラリのソースを読んでたら、見慣れないマクロがいくつかあったので、勉強しなおした。
便利そうなのをまとめておく。

マクロ名展開される値
#変数名
__FILE__ソースファイル名
__func__関数名
__LINE__ファイル内での行数

#はSRMでよく使用する。他の3つはより大規模な開発で重宝する。さらに、マクロは引数を(...)とすると、可変長引数を使用することができる。その際、可変長引数の参照は、__VA_ARGS__で行う。

以下、サンプル。
まずは、#を使った例。

#define SHOW(x) cout << #x << ": " << x << endl;

int main() {
    int x = 10;

    ++x;
    SHOW(x); // x: 11
}


次に、ログ出力。

#define DEBUG_PRINT(fmt, ...) \
    printf("In the function %s(), at line %d in %s\n", __func__, __LINE__, __FILE__); \
    printf(fmt, __VA_ARGS__)

int doit(int x, int y) {
    int z = x + y;

    DEBUG_PRINT(" x=%d, y=%d\n", x, y); // In the function doit(), at line 12107 in ..\pku.cpp
    return z*z; // x=10, y=10
}

int main() {
    int x = 10;
    int y = 10;

    int z = doit(x, y);
    cout << z << endl;
}


3つ目の例では、ログ出力のオン、オフを切り替えれるようにします。マクロの内容そのものの定義を、DEBUG_MODEが定義されているか否かで切り替えています。この書き方はかっこいいですねー。しびれます。

#define DEBUG_MODE

#ifdef DEBUG_MODE    
#define DEBUG_PRINT(fmt, ...) \
    printf("In the function %s(), at line %d in %s\n", __func__, __LINE__, __FILE__); \
    printf(fmt, __VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...)
#endif

int doit(int x, int y) {
    int z = x + y;

    DEBUG_PRINT(" x=%d, y=%d\n", x, y); // In the function doit(), at line 12107 in ..\pku.cpp
    return z*z; // x=10, y=10
}

int main() {
    int x = 10;
    int y = 10;

    int z = doit(x, y);
    cout << z << endl;
}

2011年9月3日土曜日

本当にユーザーが欲しいものは何か?

youtubeで興味深いビデオを見つけた。



簡単に要約すると、こんかんじ。

「アイディアはいろんなところからやってくる。ユーザー、技術者、役員、分析から。

そして、アイディアが出たら、次はプロトタイプだ。きちんとアイディアを実証できるものがつくれるか?どうやってユーザーのイメージを正確にとらえるか。

イノベーションをもたらすのに一番大切なのは、イタレーションである。何かを試す、フィードバックをもらう、また新しい何かを試す。これの繰り返し。そして、ユーザーが本当に求めているものに近づいていく。

googleが成功している理由は、いいアイディアを持っているから、いいアイディアをうまく実行しているから、そしてとても小さなチームで働いているからだ。

その小さなチームの中で、技術者たちは、決断をする。何がこのサービスの一番の売りなのか?
本当にユーザーが必要としているのは何か?最高の製品をどのようにつくりあげるのか?と。」

  • ユーザーが本当に欲しいものをきちんと考える。
  • 小さいチームが、決断権を持って仕事を進めていく
というのは非常に考えさせられるものがあった。

以前いたプロジェクトで以下のような話を聞いた。

「お客さんがどうしても作ってほしいっていうシステムがあった。難しい案件だったけど、なんとかがんばって作った。そのシステムの構築には、数千万円かかったらしい。でも、そのシステムができて、1年くらい経つけど、実際に使われたのって2回しかないらしいんだよね」

先輩の話によると、お客さん側のシステム部とユーザーの間で認識が違っていたらしく、システム部や経営陣が作って欲しいと言っていたシステムは、実はユーザーからすると本当は必要なシステムではなかったらしい。

おそらくシステム屋さんであれば、よく聞く話だと思う。
  • 実際にものを作る人
  • 実際にものを使う人
  • 決定を下す人
これらの間で、綿密なやりとりが無ければ、作ってみて実は要りませんでした~ってことに成りかねない。そして、これらの3種類の人間が一堂に会する機会というのはほとんどないというのが現実だと思う。

例えば、実際にものをつくる私たちシステム屋が、お客さんのユーザーと話す機会や、お客さんの意思決定権を持つひとと話すことは、ほぼない。お客さん側のシステム部という部署の人たちから要件をいただく。

細かいやりとりを抜かせば、コミュニケーションの大きな流れは以下の2つだと思う。
  • ユーザー -> (開発依頼) -> システム部 -> (意思決定依頼) -> 経営陣 -> (意思決定) -> システム部 -> (開発依頼)->開発者
  • 開発者 -> (要件、仕様確認) -> システム部 -> (確認) -> ユーザー -> (返答) -> システム部 -> (返答) -> 開発者
上記では、システム構築者を開発者としてまとめたが、実はこのシステム構築者も一次受け、二次受け、三次受け、・・・、オフショアなどと細かく階層化されている。

問題点は、2つある。1つは、情報の伝達が遅くなってしまうこと。2つ目は、ユーザーの真意が開発者に直接伝わらないことだ。大きい企業だと、このように階層化された情報伝達は仕方ないのだろうか?

私は、学生時代にアルバイトで社内システム(住所録管理、在庫管理、部品発注システムなど)を作ったことがある。
そのときは、「窓がぱっと出てきて、ボタンぽんって押したら、シュッてこの項目が別画面に飛んでボタン押したら、こうなって、・・・みたいなの作って欲しんだけど?」と言われ、「はい、分かりました。」というかんじで要件が確定し、早ければその日のうちに試作バージョンを作って、「こんな感じだけどどうでしょう?」と聞いて、「うーん、ここはもうちょっとこうなってた方がいいなー」と言われ、また作りなおして、・・
という感じで進めていた。

このやり方だとスピード感が全然違う。開発者である私と、ユーザーと、そして意思決定権のある社長。3人が画面を見て、どうするのかをその場で決める。そして、要件定義書や設計書というイメージしにくい文書で話をするのではなくて、実際にものを作って、それを改良していくので、ユーザー、開発者間の認識の差異が生まれにくい。

”アジャイル開発”という言葉が先進的な開発スタイルみたいな感じで語られることが多いが、私はこれが本来あるべき普通のシステム開発の姿のように思う。

2011年9月1日木曜日

グラフ理論の紛らわしい言葉

歩道とか経路とか小道とか紛らわしい用語の整理。こういう学術的な専門用語は日本語で覚えるより英語で覚えた方がいいと思っているので(英語で覚えると世界中のエンジニアと会話ができるから)、英語で書きます。

まず、walkというのが大きな集合としてあります。walkとは、接点と枝の列です。そしてwalkには、特に重要な以下の4つを形があります。
  • trail
  • path
  • circuit
  • cycle
まず、始点と終点が同一か否かで、trail, pathとcircuit、cycleに分かれます。問題は、trailとpathの違い、circuitとcycleの違いです。

まず、trailとpathの違いについて。trailは、含まれる枝がdistinctなものです。pathは含まれる接点がdistinctなものです。
上の図だと、1->2->3->4->2はtrailです。しかし、2の節点を2度通っているため、pathではありません。1->2->3はpathです。
 ここで思い浮かぶのは、shortest pathという言葉です。あれは、pathですよね?ダイクストラをやるときに、一度通った点はもう通らないというような処理をしているかと思います。また、Euler trailという言葉があります。あれは、同じ接点を複数回通るのでpathじゃなくて、trailなんです。同様に、Hamiltonian pathとう言葉があります。あれは、trailじゃなくて、pathですよね。

次に、始点と終点が同一になる、circuitとcycleについて。この2つも同様の基準でカテゴライズされています。circuitは含まれている枝がdistinctであるもの、cycleは含まれている節点がdistinctであるものを指します。ループが一つだけのもの(始点と終点だけ)がcycle、他にもループを持っているものをcircuitと呼ぶと考えればいいです。

以上から、集合論上は、以下の関係が成り立つかと思います。
path ⊂ trail ⊂ walk
cycle ⊂ circuit ⊂ walk