Page List

Search on the blog

2012年2月13日月曜日

Sort in Java

Played around with Java to try some sort functionalities.

What I did:
1. Just sort an integer array
2. Sort an integer array in non-increasing order
3. Sort a string array in non-decreasing order of the length
4. Sort substrings (implemented a simple suffix-array-like algorithm)

Interesting stuff:
1. Comparator is like functor in C++.
2. Anonymous class is cool! I liked this style. You can write, on the spot, an implementation of its super class (or interface).
3. Java's string data structure is clever. The way it retains strings enable to share a memory with some strings. In the code test4(), all the substrings share a memory.
4. Looks like Java Api codes are way more legible than that of C++.

Source code:
I'll post the source code below.
import java.util.Arrays;
import java.util.Comparator;

public class Test {
    public static void main(String args[]) {
        test1();
        test2();
        test3();
        test4();
    }
    
    private static void test1() {
        int xs[] = {4, 1, 4, 6, 3, 8, 10, 0};
        
        Arrays.sort(xs);
        
        System.out.println(Arrays.toString(xs));
    }
    
    private static void test2() {
        Integer xs[] = {4, 1, 4, 6, 3, 8, 10, 0};
        
        Arrays.sort(xs, new Comparator<Integer>() {
            public int compare(Integer x, Integer y) {
                return y-x;
            }
        });

        System.out.println(Arrays.toString(xs));        
    }
    
    private static void test3() {
        String names[] = {
                "hanako",
                "taro",
                "yu",
                "sho",
                "amataro",
                "takafumi"                
        };
        
        Arrays.sort(names, new Comparator<String>() {
            public int compare(String x, String y) {
                return x.length() - y.length();
            }
        });

        System.out.println(Arrays.toString(names));
    }
    
    private static void test4() {
        String text = "Oh, say can you see," +
                 "by the dawn's early light" +
                 "What so proudly we hailed" +
                 "at the twilight's last gleaming?" +
                 "Whose broad stripes and bright stars," +
                 "through the perilous fight." +
                 "O'er the ramparts we watched" +
                 "were so gallantly streaming?";
        
        String word = "gallant";
        
        // build a suffix array
        String suffix[] = new String[text.length()];
        for (int i = 0; i < text.length(); i++)
            suffix[i] = text.substring(i);
        Arrays.sort(suffix);
        
        // search the word
        int lo = 0;
        int hi = text.length() - 1;
        
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            
            if (suffix[mid].compareTo(word) < 0)
                lo = mid + 1;
            else
                hi = mid;    
        }
        
        if (suffix[lo].startsWith(word))
            System.out.println(word + " is found!");
        else
            System.out.println(word + " is not found!");            
    }
    
}

2012年2月12日日曜日

読書「共喰い」

芥川賞受賞で話題になった田中慎弥さんの「共喰い」を読みました。

「共喰い 」と「第三紀層の魚」という二話が収録されています。ストーリーそのものというよりも描写や文章で魅せる作家さんなのでしょうか?いつも読むのが伊坂幸太郎なのでストーリー展開には物足りなさを感じました。

「共喰い」の方はおもしろい表現がいくつかあって、そこに深い意味があるのか意味がないのか考えるのがなかなか難しかったです。一番謎だったのは、

「便所で小便のために引っ張り出した性器は、いま見たばかりの琴子さんの首とは全然似ていない。」

という一文です。特に意味がないと言われればそれまでですが、やたらと存在感のある文章で気に成りました。あ、でも読み終わった今考えるとこれは「父親の暴力性を受け継いだ自分」と「従順な琴子さん」を表すメタファーっぽいですね。

「第三紀層の魚」は、国語の教科書に載ってる小説みたいな感じでした。国語の教科書には載せてはいけなそうな「共食い」とは対照的ですが、両作には、”田舎”、”釣り”、”家族”、”血のつながり”といった共通点がありました。


2012年2月11日土曜日

Introduction to Algorithms chap.1

I started reading a famous book entitled "Introduction to Algorithms," which is known as a textbook used in MIT. If I read one chapter a week, I would be able to finished the book in a year. Still It would take quite a long time though. Anyway I think I'm going to write memos when I'm done with a chapter.

This is about the first chapter "The Role of Algorithms in Computing." This chapter explains what the use of learning algorithms. I found an interesting question in this chapter.

"Suppose computers were infinitely fast and computer memory was free. Would you have any reason to study algorithms?"

The answer is, of course, yes! (Truth be told, I was not able to find a witty answer by myself..)
You still want to know:
1. Your algorithm terminates?
2. If so, with a correct answer?

It also mentions quite a few areas in which algorithms really help.
  • hardware design
  • graphical user interface
  • networking
  • computer language (compiler, interpreter, or assembler)
Some people say, "In this age when the computer has developed this far in a sense of hardware, what the use of learning fast algorithm?" But as the book says, "With the ever-increasing capacities of computers, we use them to solve larger problems than ever before."

I would conclude that this first chapter is really encouraging for those who want to start learning algorithms. You should try it out.


2012年2月8日水曜日

Facebook Hacker Cup 2012 Round2

1SUBMIT/1WAというひどい結果でした。自分の今の実力でTop100に入れるなんて思ってないし、もっと練習しなければと思いました。とりあえず復習してAとBは解けたので簡単な解説を書きます。

Road Removal
先に重要じゃない都市同士を結ぶ道路をすべて採用します。するとグラフが、重要じゃない都市のみで構成される複数の連結成分と複数の重要な都市から成り立つ森になります。前者の連結成分を一つの節点とみなしてクラスカル法のようなことをすればOK。使用するアルゴリズムや実装は簡単だけどどのアルゴリズムを適用すれば良いのかが見えにくいという問題で、個人的には良問だと思います。
const int MAX = 10000;
int par[10000];
int rank[10000];

void init(int n) {
    REP(i, n) {
        par[i] = i;
        rank[i] = 0;
    }
}

int find(int x) {
    if (x == par[x])
        return x;
    return par[x] = find(par[x]);
}

bool same(int x, int y) {
    return find(x) == find(y);
}

void merge(int x, int y) {
    x = find(x);
    y = find(y);

    if (x == y)
        return;
    if (rank[x] > rank[y]) {
        par[y] = x;
    } else {
        par[x] = y;
        if (rank[x] == rank[y])
            rank[y]++;
    }
}

void solve() {
    int T;

    scanf("%d", &T);
    REP(t, T) {
        int N, M, K;

        scanf("%d %d %d", &N, &M, &K);
        init(N);

        int a, b;
        vector<pair<int, int> > road;
        REP(i, M) {
            scanf("%d %d", &a, &b);
            if (min(a, b) >= K)
                merge(a, b);
            else
                road.push_back(MP(a, b));
        }

        int ret = 0;
        REP(i, road.size()) {
            a = road[i].first;
            b = road[i].second;

            if (same(a, b))
                ++ret;
            else
                merge(a, b);
        }

        printf("Case #%d: %d\n", t+1, ret);
    }
}



Monopoly
Union Findと動的計画の合わせ技です。dp[i][j]に会社iの社長が直属の部下をj人持つときの木の高さの最小値を保持します。あとポイントとなるのは、dp[i][j]の更新を高速に計算するためにminHeight[i]に現時点における会社iが取り得る木の高さの最小値を保存しておくことです。Union Findの一度の操作をO(log(N))とすると、一つの問題はO(N log(N) + ND)で計算できます。

const int INF = 999999999;
const int MAX_N = 30000;
const int MAX_D = 5000;
int dp[MAX_N][MAX_D+1];
int minHeight[MAX_N];
int par[MAX_N];
int rank[MAX_N];

void init(int n) {
    REP(i, n) {
        par[i] = i;
        rank[i] = 0;
    }
}

int find(int x) {
    if (x == par[x])
        return x;
    return par[x] = find(par[x]);
}

void merge(int x, int y) {
    x = find(x);
    y = find(y);

    if (x == y) return;
    if (rank[x] > rank[y])
        par[y] = x;
    else {
        par[x] = y;
        rank[y]++;
    }
}

bool same(int x, int y) {
    return find(x) == find(y);
}

void solve() {
    int T;
    scanf("%d", &T);

    REP(t, T) {
        int N, D;
        scanf("%d %d", &N, &D);

        init(N);
        REP(i, N) REP(j, D+1) dp[i][j] = INF;
        REP(i, N) {
            minHeight[i] = 1;
            dp[i][0] = 1;
        }

        int a, b;
        REP(i, N-1) {
            scanf("%d %d", &a, &b);
            a = find(a-1);
            b = find(b-1);

            merge(a, b);
            int x = find(a);
            dp[x][0] = INF;
            for (int j = D; j > 0; j--)
                dp[x][j] = min(max(dp[a][j-1], 1+minHeight[b]), // a becomes the president
                             max(dp[b][j-1], 1+minHeight[a])); // b becomes the president

            minHeight[x] = INF;
            REP(j, D+1)
                minHeight[x] = min(minHeight[x], dp[x][j]);
        }

        int ret = INF;
        int x = find(0);
        REP(i, D+1)
            ret = min(ret, dp[x][i]);

        printf("Case #%d: %d\n", t+1, ret);
    }
}



2012年2月4日土曜日

Javaで文字列の変更

Javaのメモ。「Stringで渡された文字列をある条件を満たすように回文にしてStringで返せ」という問題があって、文字列の一部を変更したい場合どうするんだっけ?となったので、いくつかやってみた。
  1. char[]に変換して特定の文字を更新、その後Stringオブジェクトに戻す
  2. byte[]に変換して特定の文字を更新、その後Stringオブジェクトに戻す
  3. StringBufferを使う
  4. Stringbuilderを使う
まず、1.のやり方。簡単のため、"hogehoge2011"を"hogehoge2012"にする場合を考えます。
public static void main(String args[]) {
    String hoge = "hogehoge2011";
    
    char []chrs = hoge.toCharArray();
    chrs[chrs.length-1] = '2';
    hoge = new String(chrs);
    
    System.out.println(hoge);
}


char[]からStringに戻すときは、Stringコンストラクタにchar[]を渡してあげます。

hoge = chrs.toString();
や、
hoge = Arrays.toString(chrs);

を使ってしまいそうになりますが、間違いです。前者は、ObjectクラスのtoString()が呼ばれ「オブジェクトの派生元のクラス名、アットマーク (@)、およびオブジェクトのハッシュコードの符号なし 16 進表現から構成される文字列」を返します。こんなのがhogeに代入されます。

[C@10b30a7

また、後者のArrays.toString(chr)の場合は、以下のような文字列がhogeに代入されます。

[h, o, g, e, h, o, g, e, 2, 0, 1, 2]

ということで、char[]からStringにC++のような代入をしたい場合は、Stringのコンストラクタを使うようです。

2.は1.と同様です。Javaのcharは2バイトの領域を持っています。これに対してbyteは1バイトなので、文字コードが1バイトに収まる文字だけが対象の場合は、byte[]で扱った方が効率的です。

3. StringBufferオブジェクトを利用したやり方です。
    public static void main(String args[]) {
        String hoge = "hogehoge2011";
        
        StringBuffer sb = new StringBuffer(hoge);
        sb.setCharAt(sb.length()-1, '2');
        hoge = sb.toString();
        System.out.println(hoge);
    }

4. StringBuilderを利用したやり方は、StringBufferの場合と同様なので省略します。両者の違いが気になりますが、以下のような違いがあります。
  • 提供する機能は同じ
  • StringBuilderは高速だが、同期を保証しない
  • StringBufferは同期を保証するが、低速
ということで、可能な場合はStringBuilderの使用が推奨されています。

2012年2月3日金曜日

オイラーの多面体定理

球面と同相な多面体の頂点の数をV、辺の数をE、面の数をFとすると以下の関係が成り立ちます。
   V + F - E = 2

ここのページの説明が分かりやすかったです。

説明の概略は以下のとおり。
  1. 多面体の一つの面を取り外して平面に押しつぶして平面グラフを作る。
  2. グラフが木になるまで、枝をとりのぞいていく。(枝を1つ減らすと面の数も1つ減るのでV+F-Eは不変)
  3. 木の場合は、V+F-E=2が分かっている。
外側の無限の広さを持った空間も一つの面として考えることで、取り外した面との整合性をとっているのが面白いと思いました。

 上記のページにはもう一つ面白い関係が紹介されていました。任意の平面グラフにおいて
   2E ≧3F
が成り立つ。

この説明が分かりづらかったので調べてみました。以下のページの説明が分かりやすいかったです。

V=2, E=1の連結グラフの場合は上の関係は成り立たないので、正しくは「V≧3の連結な平面グラフ」という条件のもと、
   2E ≧3F
が成り立つ。です。

証明の概略は以下のようになります。まず大きくi)平面グラフGの辺の数が3未満の場合、ii) それ以外の場合で場合分けします。

i)のとき、V≧3という条件と合わせると、V=3、E=2、F=1の場合しかありえません。このとき、2E > 3Fなので成り立つ。
ii)のとき、すべての面において境界となる辺の数の和(以下ではSum(|e(f)|)と書きます。)を考えます。ある面の境界となる辺の数は3以上です。(ii)の条件より無限に広がる外側の面の境界辺の数も3以上であることに注意。)面はF個あるので、Sum(|e(f)|)は3F以上です。
次に辺の観点から考えます。一つの辺は多くても面の境界として数えられるのは2回だけです。(一つの辺が3つ以上の面の境界とは成りえないから。)よって、Sum(|e(f)|)は2E以下です。
以上から、2E ≧ Sum(|e(f)|) ≧3Fとなります。

等号成立は、すべての枝が2つの面の境界となっていて、かつ、すべての面が3辺によって境界づけられる場合なので、V=3のときの完全グラフだけかと思います。参照元のページには"equality holds if and only if every face is a triangle."とありましたが、三頂点の完全グラフ意外にそれを満たすグラフってあるんでしょうか。。

2012年1月30日月曜日

Facebook Hacker Cup 2012 Round1

全部解けました!!無事Round2出場決定です。Top100に残ってT-shirtsゲットしたい。。

Checkpoint
 数学の問題。パスカルの三角形を使って解きました。下のコードでは、len[i]に最短路のパターン数がiになる場合における始点-終点間のマンハッタン距離の最小値を格納しています。len[]が分かれば、答えはlen[j] + len[S/j]の最小値です(jはSの約数)。
const int MAX = 10000000;
const int INF = 100000000;
int comb[2][10000];
int len[MAX+1];

void solve() {
    for (int i = 0; i <= MAX; i++)
        len[i] = i;

    memset(comb, 0, sizeof(comb));
    comb[0][0] = 1;
    for (int i = 0; i < 10000; i++) {
        int now = i&1;
        int next = (i+1)&1;

        if (i > 0) {
            for (int j = 0; j <= i; j++) {
                int k = comb[now][j];
                if (k <= MAX)
                    len[k] = min(len[k], i);
            }
        }

        memset(comb[next], 0, sizeof(comb[next]));
        for (int j = 0; j <= i; j++) {
            comb[next][j] = min(INF, comb[next][j] + comb[now][j]);
            comb[next][j+1] = min(INF, comb[next][j+1] + comb[now][j]);
        }
    }

    int R, S;
    scanf("%d", &R);
    for (int i = 0; i < R; i++) {
        scanf("%d", &S);

        int ret = INF;
        for (int j = 1; j * j <= S; j++) {
            if (S % j == 0)
                ret = min(ret, len[j] + len[S/j]);
        }

        printf("Case #%d: %d\n", i+1, ret);
    }
}


Recover the Sequence
 実装問題。最初にord[i] = iとした配列を用意。デバッグ情報を元にマージソートを再現します。ソート完了後のord[]を見ることで、iはもともとord[i]番目にあったんだなということが分かります。
const LL MOD = 1000003;

vector<int> merge_sort(vector<int> arr) {
    int n = arr.size();
if (n <= 1)
return arr;

int mid = n/2;
vector<int> arr1(arr.begin(), arr.begin()+mid);
arr1 = merge_sort(arr1);
vector<int> arr2(arr.begin()+mid, arr.end());
arr2 = merge_sort(arr2);

unsigned int p = 0, q = 0;
for (;p < arr1.size() && q < arr2.size();) {
    int c = getchar();
    if (c == '1') {
        arr[p+q] = arr1[p];
            ++p;
    }
    else {
        arr[p+q] = arr2[q];
        ++q;
    }
}

for (;p < arr1.size(); p++)
    arr[p+q] = arr1[p];
for (;q < arr2.size(); q++)
    arr[p+q] = arr2[q];

return arr;
}

void solve() {
    int T;
    int N;
    vector<int> ord;
    vector<int> pos;

    cin >> T;
    for (int t = 0; t < T; t++) {
        cin >> N;

        ord.resize(N);
        for (int i = 0; i < N; i++)
            ord[i] = i;

        getchar();
        ord = merge_sort(ord);

        pos.resize(N);
        for (int i = 0; i < N; i++)
            pos[ord[i]] = i+1;

        LL ret = 1;
        for (int i = 0; i < N; i++)
            ret = (31 * ret + pos[i]) % MOD;

        printf("Case #%d: %I64d\n", t+1, ret);
    }
}

Squished Status
 DPの問題。dp[i][j]にcompressed messageのi番目まで見終わったとき最後の文字コードがjとなっているときの場合の数を格納すればOKです。
const LL MOD = 4207849484LL;
LL dp[1024][256];

void solve() {
    int N;

    scanf("%d", &N);
    for (int t = 0; t < N; t++) {
        int M;
        string comp;

        cin >> M >> comp;
        int len = comp.length();
        memset(dp, 0, sizeof(dp));
        dp[0][0] = 1;

        for (int i = 0; i < len; i++) {
            for (int j = 0; j <= M; j++) {
                if (dp[i][j] == 0)
                    continue;

                int d = comp[i] - '0';
                int k = 10 * j + d;

                if (0 < k && k <= M)
                    dp[i+1][k] = (dp[i+1][k] + dp[i][j]) % MOD;
                if (k != d && 0 < d && d <= M)
                    dp[i+1][d] = (dp[i+1][d] + dp[i][j]) % MOD;
            }
        }

        LL ret = 0;
        for (int i = 1; i <= M; i++)
            ret = (ret + dp[len][i]) % MOD;

        printf("Case #%d: %I64d\n", t+1, ret);
    }
}