Page List

Search on the blog

ラベル PKU の投稿を表示しています。 すべての投稿を表示
ラベル PKU の投稿を表示しています。 すべての投稿を表示

2011年12月11日日曜日

n進数表記の数を(n-1)で割れるかどうか

面白い問題があったので、紹介。

 n進数表記の数値を(n-1)で割り切れるかどうかは、すべての桁の数値を足した数が(n-1)で割れるかどうかを調べればいいです。例えば、10進数表記の111111111、45、1233、123453は桁の合計値が9で割れるので9の倍数です。

 n進数表記のm桁の数 A = A_(m-1).. A_2 A_1 A_0について考えます。つまり、
A = A_(m-1) * n^(m-1) + ... A_2 * n^(2) + A_1 * n^(1) + A_0
です。 n = 1 (mod (n-1))なので、
A = A_(m-1) + ... + A_2 + A_1 + A_0 (mod (n-1))
です。
ということで、Aの各桁の合計値を出してそれが(n-1)で割れるかどうかをみればOKです。

 C++のスパゲッティなソースを張っておきます。(dividable()の中で毎回合計値計算してるのは無駄ですね。。最初まじめにHormerの公式を使って余りを計算して解こうとしていたのの名残です。)

int num[128];

bool dividable(int k, const string &str) {
    int len = str.length();
    int digsum = 0;
    REP(i, len) digsum += num[(int)str[i]];

    return digsum % (k-1) == 0;
}

int main() {
    string str;

    for (int i = '0'; i <= '9'; i++) num[i] = i - '0';
    for (int i = 'A'; i <= 'Z'; i++) num[i] = i - 'A' + 10;
    for (int i = 'a'; i <= 'z'; i++) num[i] = i - 'a' + 36;

    while (cin >> str) {
        int len = str.length();
        int base = 0;
        REP(i, len) base = max(base, num[(int)str[i]]);
        base = max(2, base+1);

        int k;
        for (k = base; k <= 62; k++)
            if (dividable(k, str))
                break;

        if (k <= 62) cout << k << endl;
        else cout << "such number is impossible!" << endl;
    }

    return 0;
}

2011年12月8日木曜日

最大クリーク問題

http://poj.org/problem?id=3692
を解いていて、「最大クリーク問題」という問題について知った。

 与えられたグラフの部分グラフのうち完全グラフとなるものをクリーク(clique)と呼ぶ。節点数が最大となるクリークを求める問題を「最大クリーク問題」という。この問題はNP完全らしい。

 完全グラフの補グラフは、null graph(枝集合が空集合であるグラフ)であるので、あるグラフの最大クリークを求めることと、その補グラフの最大独立集合を求めることは同値である。

 二部グラフにおいては最小点カバーと最大マッチングが一致すること、および、一般のグラフにおいて最小点カバーと最大独立集合の和が節点数と一致することから、対象の補グラフが二部グラフである場合には最大クリークを簡単に求めることができる。

 ”クリーク”という言葉は、社会学から生まれたらしい。ソーシャルグラフの中からお互いが全員知り合いであるような最大集合の大きさを求めたいというのが研究のはじまり。まさに、PKUの問題もこれと同様の問題。実際のソーシャルグラフの補グラフは必ずしも二部グラフになるとは限らないため、さまざまな近似アルゴリズムが研究されているらしい。

 PKUの問題の解答例を載せておきます。

int n;
bool edge[400][400];
bool used[400];
int pr[400];

bool match(int s) {
    used[s] = true;
    REP(i, n) {
        if (!edge[s][i]) continue;
        if (pr[i] == -1 || (!used[pr[i]] && match(pr[i]))) {
            pr[s] = i;
            pr[i] = s;
            return true;
        }
    }

    return false;
}

int main() {
    int b, g, m;

    int t = 0;
    while (cin >> b >> g >> m) {
        if (!b && !g && !m) break;

        int x, y;
        n = b + g;
        REP(i, n) REP(j, n) edge[i][j] = 1;
        REP(i, b) REP(j, b) edge[i][j] = 0;
        REP(i, g) REP(j, g) edge[i+b][j+b] = 0;

        REP(i, m) {
            cin >> x >> y;
            --x, --y;
            edge[x][y+b] = edge[y+b][x] = 0;
        }

        int ret = n;
        memset(pr, -1, sizeof(pr));
        REP(i, n) {
            if (pr[i] == -1) {
                memset(used, 0, sizeof(used));
                if (match(i)) --ret;
            }
        }

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

    return 0;
}


参考:
1. simezi_tanの日記
2. Clique problem - wikipedia

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.

2011年4月25日月曜日

PKU 250問クリア

昨年の夏からずっとやっているPKU。

ようやく、250問ACした。
今年の目標は300問達成だが、おそらくこのペースだと達成できそう。

最近解いた問題で面白かったのは、
Trie木を使った問題  : http://poj.org/problem?id=2001
オイラー路の問題   : http://poj.org/problem?id=1300
でしょうか。
新しいデータ構造やアルゴリズムを学びました。

そろそろ、
  • 最大フロー
  • マッチング
あたりに手を出したいところ。

 あとは、そろそろTopCoder div.2を出たい。。多分出場回数を増やせば上がれるはず。codeforceはorange coderに上がりたいところ。。

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年1月7日金曜日

200 Accepted on POJ

I did it! I've got 200 ACs on POJ, Peking Online Judge, which is the biggest computer algorithm exercise platform!
The next target is, of course, 300 ACs.

Looking back on days after I got 100 ACs, what happened?
What I got? Or what I learned?

umm, I got a little familiar with the game tree, DFS, and the union find tree.
oh, last and not least, I learned a lot about the bit manipulations -- bit DP, the inclusion and exclusion principle --.

The problem left unsolved on POJ is getting more and more difficult.
I barely come up with an approach to the problems easily these day.
And the more problems I solve, the more I notice that I need lots of practices.

What I'm interested in now?
Well, hash algorithm, especially string manipulations against a large mount of text such as Rabin-Karp string search algorithm.
I understand how the hash function works and how it cuts the time to calculate the hash value. But I cannot apply it to several problems on POJ.

And also, I'm curious about the matching problem.
Both the matching problem in bipartite graph and the general matching problem.
But it seems it's a little difficult to me for now.
If you can solve this problem, could you tell me how to solve it??

Ahh, I get hungry. I have to go eat something.
See you.
Thanks for reading.

2010年12月25日土曜日

ビット演算集大成の問題!?

ビット演算集大成の良問をPOJで見つけたので紹介。

実は、自分でも似たような問題を考えていてTopCoderに問題投稿しようかな・・と思ってましたが、似たような問題は既にありました。。
しかも、2次元バージョンでよりトリッキーな感じ。。

問題はこちら。

簡単に日本語訳すると、
4*4マスに+と-の文字列がある。
(i, j)マスを選択すると、(i,j)マスが
+の場合は、-に
-の場合は、+に
反転変換できる。但し、(i,j)マスを選択すると、i行のマス及びj行のマスの値もすべて反転してしまう。
すべての文字を-にするためには、最低何回のマスを選択しないといけないか?
またそのときに選択するマスを列挙せよ。

んー、これ系の問題はメジャーなんですかね。。TopCoderでも似たような問題ありました。
ビット演算を使うと解けますが、実はこの問題はビット演算の使いどころが2種類あります。
①組み合わせをビットで表現する
②状態および処理をビット演算で計算し、処理を高速化

①は、どのマスを選択するかを0, 1のビットで表すという意味。
②は、4*4マスの状態を16bitのビットで保持します。さらに、あるマスを選択した場合の処理をXORを用いて実現します。

以下ソース。


int main() {
int ch[16];
REP(i,16) {
ch[i] = 0;
REP(k, 16)
if (k/4 == i/4 || k%4 == i%4)
ch[i] |= 1<<k;
}

string in;
int ref = 0;

REP(i, 4) {
cin >> in;
REP(j, 4)
if (in[j] == '+')
ref |= 1<<(4*i+j);
}

int ret = INF, bcomb = -1;
REP(comb, 1<<16) {
int ref_t = ref, cnt=0;
REP(i, 16)
if (comb >> i & 1)
ref_t ^= ch[i], ++cnt;

if (!ref_t && ret > cnt)
ret = cnt, bcomb = comb;
}
cout << ret << endl;
REP(i, 16)
if (bcomb >> i & 1)
cout << i/4+1 << " " << i%4+1 << endl;

return 0;
}


2010年10月27日水曜日

PKU150問突破~~!

PKU150問突破した。
やった~~~。わーーい^^

動的計画にだいぶ慣れてきた。それからグラフの問題が解けるようになった。DijkstraとかWarshall-Floydとか基本的なアルゴリズムを勉強した成果だろう。
あとは、Union-Findとか包除原理とかモジュロ演算とかをもっと勉強したいところ。。

アルゴリズムコンテスト用のコーディングスタイルもだいぶ染みついてきた。
  • 変数の名前はなるべく1文字にする
  • 変数はグローバル変数として宣言する
  • なるべく改行しない
  • マクロを駆使する
などなど。。

とりあえず次の目標は、200問。そしてターゲットはKyushu Univ. Topのyuta_ihcarokさん。
あとは、TopCoderの過去問やるのもありかなと思う。こっちは過去問解いても順位が付かないからモチベーションは上がらないけど。。

2010年9月26日日曜日

TopCoder惨敗とPKU祝杯!

昨日開催されたTopCoder SRM 483(division 2).

Easyに手間取り8分くらい使ってしまった。良く見ると、途中で変数の名前が入れ替わってた(笑)焦りは禁物ですね。
Middleは、20分かからないくらいで解けた。これは調子がいい。
初めての本格的なHardの挑戦。なんか行ける気がする。。結局行けなかった・・・・。

そして、チャレンジフェーズ。まさかのMiddle撃沈。ショックを受けている間に、次々と撃沈されるソース達。いったい何が・・
最終的には、部屋の中の上2人以外はMiddleが撃沈されている始末!これは前代未聞の出来事では。。
撃沈されていない人のソースをみると、、、、なるほど1人のときは行と列のうち片方見るだけでいいのか。。確かに。。次回からは、例外っぽいケースはきちんと確認するようにしよう。

そして、PKU。
今日、ついに念願の100問、突破~~~。。
意外と早く達成できて驚いている。次の目標は150問。しかし、そろそろ簡単な問題が無くなってきている。ここからの問題はなかなかの猛者達ばかりだろう。
ワンピースで言うと『グランドライン』到達くらい。。
がんばろう!!targetは東北大学のmatsuさん。。すぐに抜かす。

2010年9月8日水曜日

PKU Top10,000入り!!

ついにPKU上位10,000に入りました。
思ったより早く達成できました。
やっぱり、地道に解くことが大切だと痛感しています。
日々の小さな積み重ねが山となるのです。

まあ簡単な問題しか解いてないような気がしますが・・・・

これから更に上に行こうと思うなら、簡単な問題を解くだけでは厳しいでしょう。
次の目標は、上位5000位!!

頑張ります!

最近思うのは、問題を解くだけではダメだということです。
やはり、解くだけだと理解が自分の中で閉じてしまいます。
自分の解いた問題の解説や問題から学びとったTips、テクニックを記録して公開することで、より理解が深まると思います。
例えば、この人。すごいです。

私も解いた問題の難易度・概要・感想などをまとめているので、そのうちデータベース化してGoogle App Engine上にでも公開しようかと思います。

2010年8月18日水曜日

怒涛の4AC★

PKUハマりすぎ。。笑
今日は4問解いた。。

出力フォーマットの形式ミスと、define入れ損ねてコンパイルエラーになった凡ミスを除けば4連続一発AC。
まー簡単な問題しかやってないんだけど(笑)
でも、ランキングを確認すると、Top10%入りが目前に迫ってきた。。やっぱTopCoderと違ってやればやるほどランキングが上がるというのは良いモチベーションになるみたいだ。

今日やった問題で、どうやら自分の苦手分野が分かってきた。
こういうのは得意。
与えられた複数のロープを使って持ちあげられる荷物の重量の最大値を返せ。但し、ロープには均等に重さがかかるとする。それぞれのロープは荷物にくくりつけても、つけなくてもよい。

こういうのが苦手。
1からNまでの素数をリスト化する。リストのサイズが偶数個の場合は、C*2個、奇数の場合はC*2-1個リストの中央部分から抽出せよ。

簡単に言うと数学チックなのは得意だけど、配列の添え字の微妙な感覚(-1するべきか、しないべきかなど)が苦手みたい。。

Topcoderでは、1595みたいな問題に苦戦する。
まー慣れなんだろうけど。。。こういう問題いっぱい解いた方がいいのかな。。

2010年8月14日土曜日

二分探索をやってみよう!

昨日PKUにて、解き方の方針は立ったがどうしようか・・という場面に遭遇した。

簡単にいうとこんな感じ。
1 - 50,000までの数の中で、ある条件を満たす数の最大値はいくらか??数が小さければ小さいほど条件を余裕をもって満たせることは明らか。。
じゃー、1-50,000までインクリメントしてやってみるか。。。

いや、待て。そんなんじゃ時間足りない気がする。条件を満たすかどうか調べるの結構時間使いそうだし。じゃあどうしよう・・・
そこで閃いた。

①まず、25,000で試してみよう。
②25,000で条件を満たすのなら、今度は25,000 - 50,000までの探索をしよう。満たさないなら0-25,000まで探索しよう。

①②の操作で探索範囲が1/2になった。同じことを逐次10回やれば探索範囲は大体1/1000くらいになるな。20回やれば1/1,000,000くらいにできるな。。
単純にインクリメントした場合は、最悪50,000個の数を調べないといけないけど、この範囲を1/2に狭めていく方法を使えば、最悪でも16回以下くらいで探索ができるな!!

ん・・何かこれ、もしかしてBinary Searchとか言うやつじゃないのか。
ググってみると、ありました。Binary Search(二分探索)。そうそうこれこれ。。

早速ちょっと試しに簡単な例題を作って練習してみました。

例題
単調増加関数fがある。ある自然数nが与えられた時、f(x) > nとなる最小の自然数xを求めなさい。
ここで、f(x) = (int)log(x) + (int)sqrt(x) + 1とする。

単純な線形探索とBinary Searchで実行速度を比べてみよう。
ソースはこんな感じ。


using namespace std;

int func(int n) {

return (int) log(n) + (int)sqrt(n) + 1;
}

int main() {
int target = 7777;
int i = 0;

// Linear Search
while (1) {
if (func(i) > target)
break;
i++;
}
cout << i << endl;

// Binary Search
int left = 0;
int right = INT_MAX;
i = left + (right - left) / 2;
while (1) {
if (func(i) > target && func(i - 1) <= target)
break;

if (func(i) > target)
right = i;
else
left = i;
i = left + (right - left) / 2;
}
cout << i << endl;
}
ちなみに x= 60,217,600だが、探索にかかった時間は
Linear Searchの場合は、12.347秒。Binary Searchの場合は、0.000000011秒(笑)。
全然違います。

他にもBSはいろいろな使い道がありそうです。
DPと並んでアルゴリズムコンテストでは必須アイテムの一つと言えるでしょう。

ちなみにこれが昨日遭遇したBinary Searchを使って解くPKUの問題。興味のある人はチャレンジしてみて下さい!僕も実装はまだです。。。

2010年8月12日木曜日

この夏、PKUが熱い!!

先日始めたPKU!

中々熱いYo! PKU!

You の順位付くYo!!


あれ・・・・
なんかラップ調に。。。
ちょっと、疲れてるのかな。。

とにかく、PKUが熱いです!!なんと解いた問題数に応じて順位が付くじゃありませんか!!
今のところ、42813位(177050中)くらい。

PKUをお勧めする理由は3つあります。(「3つあります。」はうちの会社の社員の口癖(笑)彼らは何でも3つあるみたいです。。)

①いつでも解ける。
 Topcoderと違っていつでも解けます。
②時間制限がない。
 時間制限がなく、単純に解けた問題数で順位が付くみたいです。
③問題が豊富である。
 Topcoderにはないような問題があります。例えば、多倍長整数の演算や、STLを使用するとそのオーバーヘッドにより解けない問題(TopCoderはSTLの使用が基本ですよね。。)など。。問題量が豊富なので、問題の難易度も豊富です。自分のレベルにあった問題を探して解けばかなりレベルアップができます。

とりあえず目指すは、Top1%のプログラマーですかね!
300問くらい解けば、1%に入れるみたい。。。毎日1問ペースでやれば、1年後には、

「あ、おれ、世界のプログラマーTop1%に入るんだよね!」

って言えます(笑)
300問解ければ、TopCoderでも黄色くらいにはなれるかな・・・


2010年8月10日火曜日

PKUを始めてみた!

PKUを始めた。。

PKUは、北京大学がストックしているプログラミングコンテストの過去問題集である。
世界中で開催されたプログラミングコンテストの過去問がのっているのでなかなか勉強になる。

しかも、PKUを問題の種類別に(探索/動的計画/貪欲法など)カテゴライズしてくれているサイトがあったり、最重要問題をピックアップしてくれていたりするサイトがあるので、自分の弱点を重点的に補強することが可能である。

問題自体は、登録なしでも見れるが、submitしてソースコードの正当性を確認するためには、PKUのサイトに登録しなければならない。。(まーでも、ちょっとした情報入れるだけなんで30秒くらいで出来ます。)
使ってみた感想は、Google Code Jamに似ている気がする。フォーマッティングされたinputを読み取り、outputを指定のフォーマットで出力する。。

とりあえず、今日は、最近勉強しているDPを解いてみた。
問題は、これ。

比較的簡単な問題だった。。
普通にコーディングすると最悪計算量がO(2^(n+m))になってしまう典型的なクラスNPの問題。。
ちょっと工夫すれば、O(n*m)で解けます。。。(ここで、n, mはそれぞれ文字列A,Bの長さとする。)

今回は、「PKUってのがありますよ」って紹介だったので、ソースは掲載しませんが(手抜きですいません。。)、2次元のマトリックスが頭の中に浮かんで、左下からスタートして、上に行くか、右に行くか、を逐次選びながら進んで、一番右上にゴールしたときに・・・・みたいなイメージが頭の中に浮かぶといいです。。
DP知らない人からすると、何で文字列の問題が、経路問題みたいになるんだろう???って感じだと思いますが。。そういう人は、是非ともDPの勉強をお勧めします。。
お勧めのサイトはこれ。