Page List

Search on the blog

2011年10月9日日曜日

オイラーの定理についてちょっとだけ

Google Code Jam Japan決勝の問題B「バクテリア」が非常におもしろかったので、オイラーの定理について以下の本で復習したことをまとめておきます。(数論の入門書としてかなりお勧めです。オイラーの定理、フェルマーの小定理、拡張ユークリッド互除法、中国の剰余定理などプロコンに頻出の定理はほぼ網羅しています。)




※一部自分の考えも入っているため、間違えている箇所がありましたらご指摘ねがいます。

 a^N を nで割った余りについて考えます。n = p1^k1 * p2^k2* .... pm^kmと素因数分解できるとします。このとき、a = A * p1^a1 * p2^a2 * .... pm^amとおきます。(Aはnと互いに素な数)。考えられるパターンは、以下の3つです。
  1. a1 = a2 = .... am = 0のとき(aとnが互いに素なとき)
  2. a1 >0, a2 > 0, ... am >0のとき(aがnの素因数をすべて含むとき)
  3. その他のとき(aがいくつかのnの素因数を含むとき)

 1. はオイラーの定理より、a^φ(n) = 1なので、φ(n)はa^Nをnで割った余りの列の周期になります。

 2. のとき。a1 * N >= k1, a2 * N >= k2, ..., am * N >= kmとなるようなNに対してa^Nをnで割ったあまりは0となります。十分に大きなNに対しては、φ(n)はa^Nをnで割った余りの列の周期になります。"十分に大きな"とはどれくらいか?式を見た感じだと、かなり悪く見積もっても最悪でN = nとすればよさそうです。

 3. のとき。これは長くなるので書けませんが、
φ(n) = φ(p1^k1) * φ(p2^k2) * .... φ (pm^km)
となるのを利用して、mod(pi^ki), i = 1,2, ..., mにおいてa^Nを評価して結果を中国の剰余定理で統合するようなことをしています。この場合もφ(n)はa^Nをnで割った余りの列の周期になります。
断言はできませんが、こちらも式を追う限りだと、最悪でもN=nとすれば周期的になりそうです。

 プログラムでテストしてみます。

const int MAX_N = 1000;

int gcd(int a, int b) {
    if (b == 0)
        return a;
    return gcd(b, a%b);
}

int euler(int x) {
    int ret = x;

    for (int i = 2; i * i <= x; i++) {
        if (x % i == 0)
            ret = ret * (i-1) / i;
        while (x % i == 0)
            x /= i;
    }
    if (x > 1)
        ret = ret * (x-1) / x;

    return ret;
}

int modPow(int x, int p, int mod) {
    int ret = 1;
    while (p > 0) {
        if (p & 1)
            ret = ret * x % mod;
        x = x * x % mod;
        p >>= 1;
    }
    return ret;
}

bool cover(int x, int n) {
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0 && x % i > 0)
            return false;
        while (x % i == 0)
            x /= i;
        while (n % i == 0)
            n /= i;
    }
    if (n > 1 && n != x)
        return false;
    return true;
}

bool assert1(int x, int mod) {
    int v = euler(mod);

    if (modPow(x, v, mod) != 1) {
        fprintf(stderr, "Error1 %d, %d", x, mod);
        return false;
    }
    return true;
}

int assert2(int x, int mod) {
    int y = 1;
    int i = 0;
    for (; y > 0; i++)
        y = y * x % mod;

    if (i > mod) {
        fprintf(stderr, "Error2 %d, %d", x, mod);
            return -1;
    }
    return i;
}

int assert3(int x, int mod) {
    set<int> used;
    int y = x % mod;

    int i = 0;
    for (; !used.count(y);i++) {
        used.insert(y);
        y = y * x % mod;
    }
    if (i > mod) {
        fprintf(stderr, "Error3 %d, %d", x, mod);
            return -1;
    }

    return i;
}

int main() {
    // consider sequences i^k mod j

    int passed1 = 0;
    int passed2 = 0;
    int passed3 = 0;

    int loop2 = 0;
    int loop3 = 0;
    for (int i = 1; i < MAX_N; i++) {
        for (int j = 2; j < MAX_N; j++) {
            int g = gcd(i, j);

            if (g == 1)
                passed1 += assert1(i, j); // eurler's theorem
            else if (cover(g, j)) { // j has all prime factor of i
                int loop = assert2(i, j);
                passed2 += loop > 0;
                loop2 += loop;
            }
            else {
                int loop = assert3(i, j);
                passed3 += loop > 0;
                loop3 += loop;
            }
        }
    }

    int total = passed1 + passed2 + passed3;
    printf("case1 event prob: %.2lf\n", 1.*passed1/total);
    printf("case2 event prob: %.2lf average loop: %.2lf\n", 1.*passed2/total, 1.*loop2/passed2);
    printf("case3 event prob: %.2lf average loop: %.2lf\n", 1.*passed3/total, 1.*loop3/passed3);

    return 0;
}

以下、実験結果。ランダムに2つの正の整数を取ったとき、半分以上の確率でお互いに素になるようです。iがjの素因数をすべてカバーするケースはまれでMAX_Nが大きくなるに従って減っていくように見えます。また、i^nがmod jにおいて周期的になるのはn < MAX_Nと見積もっておけば十分だといえます。(case 2については、式と結果から考えてlogオーダーっぽい。)

MAX_N = 100
case1 event prob: 0.61
case2 event prob: 0.09 average loop: 2.07
case3 event prob: 0.30 average loop: 7.57

MAX_N = 1000
case1 event prob: 0.61
case2 event prob: 0.02 average loop: 2.70
case3 event prob: 0.37 average loop: 46.79

k-smooth numberの分布を考える

そろそろ100桁くらいの数の素因数分解でもチャレンジしたいなと思ってるけど、general number field sieveは難しすぎてまだ無理。ちょうど手ごろな感じのquadratic sieveというのがあったので、これなら実装できそうである。110桁程度までだと、GNFSよりQSの方が効率がいいらしい。

 さて、このquadratic sieveだが、大抵の数は小さな素因数に分解できるというところが大きなポイントとなる。一般に、最大素因数の数がk以下の正の整数をk-smooth numberと呼びます。では実際に、smooth numberってどれくらいあるのでしょうか?

 以下の問題設定をします。
[1, N]までの正の整数に含まれるk-smooth numberの数がN/2を超えるのはkがいくらのときでしょうか?

このkが小さければ、小さいほどQSを実装する側としては嬉しいわけです。他にも面白い解法がありそうですが、取りあえずパソコンのパワーを信じてがりがりエラトステネスで篩にかけます。普通のエラトステネスの場合は、素数か合成数かをマークしますが、今回は最大の素因数をキャッシュしていきます。


const int MAX_N = 1000;
int fac[MAX_N+1];
map<int, int> smooth_pop;

int main() {
    memset(fac, 0, sizeof(fac));
    fac[1] = 1;

    for (int i = 2; i <= MAX_N; i++) {
        if (fac[i] > 0)
            continue;
        for (int j = i; j <= MAX_N; j += i)
            fac[j] = i;
    }

    /* for debug
    for (int i = 1; i <= MAX_N; i++)
        cout << i << ": " << fac[i] << endl;
    */

    smooth_pop.clear();
    for (int i = 1; i <= MAX_N; i++)
        ++smooth_pop[fac[i]];

    int cnt = 0;
    for (map<int, int>::iterator itr = smooth_pop.begin(); itr != smooth_pop.end(); itr++) {
        cnt += itr->second;
        printf("%d %.3lf\n", itr->first, 1.*cnt/MAX_N);
    }

    return 0;
}

以下、Nの値と、k-smooth numberの数がN/2以上になるkの値です。

Nk
10^343
10^63257
10^853773

 10^8程度の数をquadratic sieveで分解したい場合は、53773-smooth numberまで調べれば、2個に1個は使える数が入っているということです。篩にかける際に、実際に必要となるメモリのサイズは、p(53773)の二乗程度です。p(x)はxが何番目の素数かを表します。p(x)はここでは素数定理を用いてx / log(x)で概算します。p(53773) = 4937です。4937 * 4937程度の行列なら特別なこと(非ゼロの要素だけもつようなデータ構造)をしなくても、普通にメモリに格納できます。この感覚だと100桁程度の数をQSで分解しようとすると、スパースな行列を扱うためのアルゴリズムが必要になりそうです・・・。

以下、k-smooth numberが{i | i = 1,2 ... N}の集合全体の何%を占めるかを示します。横軸がk、縦軸が占める割合です。予想どおりのグラフの形になりました。上から順にN=1,000、 1,000,000、 100,000,000のときです。




2011年10月8日土曜日

apacheのログを見て遊ぶ

最近、apacheのログの見方が分かるようになりました。いろいろ整形して遊んでみると面白いです。デフォルトでは、/var/log/apache2にログが吐かれます。まだ基本的なことしかできないですが、一応まとめ。

1. 直近のアクセスログを見る
tail access.log

2. 自分のローカルマシン以外のアクセスログを見る(自分のマシンのIPがxx.xx.xx.xxの場合)
grep -v xx.xx.xx.xx access.log

3. ユニークユーザー数を見る
cat access.log | awk '{print $1}' | sort | uniq | wc -l

4. アクセス元を訪問数の多い順にソートして表示する
cat access.log | awk '{print $1}' | sort | uniq -c | sort -r

5. リファラー(どこからこのページにやってきたか)を多い順にソートして表示する
cat access.log | awk '{print $11}' | sort | uniq -c | sort -r

4., 5. で$1、$11などと書きましたが、これはapacheの設定によって違うので、自分の環境にあわせて適当に変えてください。
あと、IPから場所を特定できるサイトがあるので、これと組み合わせるとおもしろいです。Google Analyticsだと都市名までしか出てきませんが、このサービスを使うと、大学名や企業名まで分かります。有名な海外大学からアクセスがあったりするとテンションがあがります。このサイトからは、APIが公開されていてHTTPでxmlデータを取り込むことができます。非商用のクエリは100件まで無料ということです。アクセス元のIDをためていって、適当なタイミングでAPIを流すようにして、アクセス元IP - 場所の対応データを溜めていくのもおもしろいかもしれません。

Google Code Jam Japan 2011決勝

113位でGoogle T-shirtsゲットです!!Aが早く解けてしまって、暫く10位台だったので、かなり興奮してましたが、その後何もできませんでした。BとCトライしましたが、WAでした。やったことを書きます。

A. アンテナ修復
 角度が等しいので、三角形の面積は辺の長さによってのみ決まります。長い辺同士を掛けた方がお得なので、greedyに入れて行きます。アンテナが円の形をしているので、双方向キュー(STLのdeque)を使って実装しました。本番は直観のまま突き進んで、今回は運よくACだったけど、証明せずにsubmitっていうのはあまりよくない習慣です。ちょっと証明を考えてみました。

 一番長いエレメントをx1, 二番目に長いエレメントをx2とおく。このとき、私の解法では最適解においてx1 * x2の項が存在しています。これを背理法で証明してみます。
もし、x1*x2の項がないと仮定する。このとき、ある項xp * xqのxqとx2を交換する。すると、x1*xq、xp*x2という項ができる。もとの最適値に対して、この解は、-(x2 - xq) * x1 + (x2- xq) * xp = (-x1 + xp) (x2 - xq)だけ変化したことになります。この変化分は負なので、x1*x2という組み合わせは最適値に含まれます。

以下同様にしてgreedyにとっていけばOKです。

B. バクテリアの増殖
 ただの繰り返し二乗法じゃない?と思ってやったけど最後のサンプルがあわない。何度やってもあわない。Aの採点方式が間違っているというアナウンスがあったため、Bのサンプルおかしいんじゃない?と疑いだす始末。暫くしてレッドコーダーの人達がACし出すので、おそらく何か勘違いしてるんだろうなと思いだす。コンテスト終わって、modのところで大きな勘違いをしているのに気付きました。

 A ^p (mod N) は、 A^(p mod N) (mod N)とは違いますね。。何やってんだか。。この問題、オイラーの定理使って周期出すだけじゃんと思いましたが、AとCがco-primeじゃない場合もあるので、難しいですねー。。十分に大きな値に対してはφ関数の値が周期になるはずなので、これを使えばいいっぽいです。十分に大きなって微妙な表現だけど、どれくらい大きければいいかは数論の本を見て復習しなければ・・。

C. ワイルドカード
 largeは諦めて、small狙いでいきました。単純な全探索でいいのですが、C++で正規表現関数を自作して、sampleは何とか通せたけど、submitしたらWA食らいました。暫くバグ取りと格闘して、Pythonでやろうと迷いましたが、暫くPython書いてないので頑張ってC++でごりごりやってたら時間切れ。言語によって差が出てしまうような問題はあまり好きじゃないですが、問題に応じて適切な言語を使うことができるのも技術者として重要な能力の一つと考えれば文句は言えません。

 コンテスト後、Pythonで復習しました。

import re

def smaller(x, y):
if len(x) < len(y):
return True
if len(x) > len(y):
return False
if x.count("*") < y.count("*"):
return True
if x.count("*") > y.count("*"):
return False
return x < y

def solve():
A = raw_input()
B = raw_input()

ret = "*" * 1024;
L = len(A)
for mask in range(1<<L):
At = ""
for i in range(L):
if (mask >> i & 1):
At += A[i]
else:
if len(At) == 0 or At[-1] != '*':
At += "*"

if not re.match("^" + At.replace("*", ".*") + "$", B):
if smaller(At, ret):
ret = At

return ret

T = int (raw_input())
for t in range(T):
print "Case #%s: %s" % (t+1, solve())

2011年10月7日金曜日

プロシージャやサブクエリでLIMIT句を使う場合の注意点

 MySQLでストアードプロシージャ作っていて詰まったのでメモ。limitで指定する値をプロシージャの引数で与えようとしましたが、エラーが出ました。

[ダメな例]
delimiter //
DROP PROCEDURE IF EXISTS `LRU_WORDS`//
CREATE PROCEDURE LRU_WORDS(IN p1 INT)
BEGIN
SELECT * FROM MyDictionary ORDER BY last_delivered LIMIT p1;
END
//
delimiter ;

LIMITは定数じゃないとダメらしいです。

 prepared statementを使う場合は、以下のように変数を利用できます。
一旦ユーザー変数に代入しないとダメなようです。なぜ引数から直で行けないのか・・・

[良い例]
delimiter //
DROP PROCEDURE IF EXISTS `LRU_WORDS`//
CREATE PROCEDURE LRU_WORDS(IN num INT)
BEGIN
SET @limit_num = num;
PREPARE SET_STMT FROM 'SELECT * FROM MyDictionary LIMIT ?';
EXECUTE SET_STMT USING @limit_num;
END
//
delimiter ;

これで解決。

 上のような単純な構文だとOKだけど、LIMITを用いたクエリをサブクエリとして使いたいときにはさらにひと癖あるみたいです。LIMIT句を含むクエリはサブクエリとして利用できないため、以下のように仮想テーブルに一旦落とす必要があります。


2011年10月4日火曜日

What numbers can Ax + By represent?

It's well known that, when two integers A and B are given, N can be represented as Ax + By when N can be dividable by (A, B). So when A and B are relatively prime, that is (A, B) = 1, All the integers can be represented as Ax + By for some integer (x, y).

But what if when a restriction is given that x and y are positive integers?
For instance, let's consider (A, B) = (4, 3). Since two numbers are co-prime, you can represent 1 as 4 * (-2) + 3 * 3 = 1. But here y is a negative integer. Other than (x, y) = (-2, 3), many other candidate solutions will hit your mind-- (-11,15), (-8,11), (-5,7), (1,-1), (4,-5), (7,-9), and so forth--. But all of them have a negative integer.

In general, the following holds:
When A and B are relatively prime, A x + B y can represent all the numbers above and equal to 2AB for some positive integers (x, y).

The following graphs are self-explainary.
The first one is plotting 4x+3y = 0. The second 4x+3y = 1, the third 4x+3y=11, a case where a feasible solution exists. And the last one shows why Ax + By can represent all the numbers over 2AB.








2011年10月3日月曜日

サーバー奮闘記(15) php CLI版インストール

phpをコマンドラインから使おうと思ったけど、使えなかった。
CLI(Command Line Interface)版のphpがインストールされていなかったことが原因だった。
ということで、CLI版のphpをインストールした。

$ sudo apt-get install php5-cli

これで行けるはず。以下のコマンドでバージョンが表示されればOKです。

$ which php

CLI版のphpは何かと重宝しそうです。perlでもいいんですけど、個人的にperlは象形文字みたいで気持ち悪くてあまり好きじゃないんです。あと今回やりたかったテーマは、MySQLからデータを引っ張ってきてバッチ処理をするだったので、すでにCGIではMySQLの設定が出来てるphpでやりたかったのです。

とりあえず、データはCGIでやるような感じで引っ張ってこれました。

#! /usr/bin/php -q

<?php

// connect to MySQL Server
$link = mysqli_connect("host", "usr", "passwd", "db");
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

// send a query
$query =
"SELECT
dic.word,
sample.sentence
FROM
SampleSentences sample right outer join MyDictionary dic
ON
sample.word_id = dic.id";

$result = mysqli_query($link, $query);
if (!$result) {
print("An error occurred while processing SQL queries.<br>\n");
exit();
}

// show word lists
$word = "";
while ($row = mysqli_fetch_array($result))
print $row['word'] . " " . $row['sentence'] . "\n";
?>

CLI版のphpの説明はここが詳しそうですかね。