Hatena::Grouptopcoder

TopCoder煮ブログ

本家ブログはこっち → http://d.hatena.ne.jp/nitoyon/

2013-10-06

HexagonalBoard (SRM 593 Div1 Easy)

| 21:42 | HexagonalBoard (SRM 593 Div1 Easy) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - HexagonalBoard (SRM 593 Div1 Easy) - TopCoder煮ブログ

方針は http://topcoder.g.hatena.ne.jp/nitoyon/20131005/1380995960 で固まっていたので、あとは書くだけ。

単純ミスで 1 回、System Test 落ちたあと、再提出で pass。ダメな感じ。

あと、最大 3 色だというところの証明がパッと出てこない。

#include <string>
#include <vector>
#include <iostream>
#include <map>
#include <sstream>
#include <algorithm>
#include <set>
#include <numeric>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
using namespace std;

// -xx
// x*x
// xx-
int neighbor[][2] = {{0,-1},{1,-1},{-1,0},{1,0},{-1,1},{0,1}};

class HexagonalBoard {
    bool visited[50][50];
    int color[50][50];
    vector<string> b;
    int N;
public:
    int search(int x, int y, int col) {
        visited[y][x] = true;
        color[y][x] = col;
        int ret = 1;
        for (int i = 0; i < 6; i++) {
            int xx = x + neighbor[i][0];
            int yy = y + neighbor[i][1];
            if (xx < 0 || yy < 0 || xx >= N || yy >= N) {
                continue;
            }
            if (b[yy][xx] == 'X') {
                ret = max(ret, 2);
                if (visited[yy][xx]) {
                    if (color[yy][xx] != -col) {
                        ret = 3;
                    }
                } else {
                    ret = max(ret, search(xx, yy, -col));
                }
            }
        }
        return ret;
    }

    int minColors(vector <string> board) {
        b = board;
        N = b.size();
        memset(visited, 0, sizeof(visited));
        memset(color, 0, sizeof(color));
        int ret = 0;

        for (int y = 0; y < N; y++) {
            for (int x = 0; x < N; x++) {
                if (b[y][x] == 'X' && !visited[y][x]) {
                    ret = max(ret, search(x, y, 1));
                }
            }
        }
        return ret;
    }
};

MayTheBestPetWin (SRM 593 Div1 Medium)

| 23:37 | MayTheBestPetWin (SRM 593 Div1 Medium) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - MayTheBestPetWin (SRM 593 Div1 Medium) - TopCoder煮ブログ

時間をかけても全く分からないので http://topcoder.g.hatena.ne.jp/kojingharang/20131006#1381050540Java 2 位の uwi さんのソースで理解した。

  • 選手 n ごとに最速タイム A[n] と最低タイム B[n] が分かっている (2 <= N <= 50, 1 <= A <= 10,000, 1 <= B <= 10,000)。
  • 2 つのチーム S, T に分けてリレーを行う。チームの人数は違ってもよい。
  • 一番緊迫する試合を見たいので、とりうる時間差が最小となるようなチームわけにしたい。このときの時間差を求める。

総当りで実装すると、すべてのチーム分けに対して

max(|S fast - T slow|, |S slow - T fast|)

の最小値を求めればよい。2 ^ 50 通りなので爆発して総当りは不可能。


SumA, SumB を計算しておく。

次のようにする。

  • SumA = ΣA = A[0] + A[1] + ... + A[n-1]
  • SumB = ΣB = B[0] + B[1] + ... + B[n-1]
  • C[i] = A[i] + B[i]

とする。

    int calc(vector <int> A, vector <int> B) {
        int SumA = 0, SumB = 0;
        int N = A.size();

        // SumA, SumB を計算
        for (int i = 0; i < N; i++) {
            SumA += A[i];
            SumB += B[i];
        }

ここで数式変換。

|S fast - T slow| = |ΣA[s] - ΣB[t]|
                  = |(SumA - ΣA[t]) - ΣB[t]|
                  = |SumA - ΣC[t]|
|S slow - T fast| = |ΣB[s] - ΣA[t]|
                  = |(SumB - ΣB[t]) - ΣA[t]|
                  = |SumB - ΣC[t]|
(s in S, t in T)

となる。

あとは、ΣC[t] のとりうる値が分かればよい。イメージで言ってしまえば、SumA, SumB のなるべく中間の値を取るようにグループ T を選びましょう、という問題になる。

DP

といっても、グループ T の取り方はたくさんある。ここで DP が登場。時間をキーに DP する。

ΣC[t] のとりうる範囲は 0~SumA + SumB の中に限定される。この範囲の bool な配列 dp をつくって、とりうるなら true、とらないなら false として調べていく。

  • すべての選手がチーム S にいるケースでは ΣC[t] は 0。dp[0] のみを true にしておく。
  • 選手 0 がチーム S に参加する可能性を考えると、ΣC[t] は 0 か C[0] をとりうる。dp[0+C[0] ] を true にする。
  • 選手 1 がチーム S に参加する可能性を考えると、ΣC[t] は 0, C[0], C[1], C[0] + C[1] をとりうる。dp[0+C[1] ] と dp[C[0]+C[1] ] を true にする。

こんな感じで 0~SumA + SumB の範囲について、ΣC[t] がとりうる範囲のフラグを立てていく。

        // 時間をキーにした dp 配列を準備
        // C[i] = A[i] + B[i] が取る得る値かどうかを格納する
        bool* dp = new bool[SumA + SumB + 1];
        memset(dp, 0, sizeof(bool) * (SumA + SumB + 1));
        dp[0] = true;
        for (int n = 0; n < N; n++) {
            for (int i = SumA + SumB; i >= 0; i--) {
                if (dp[i]) {
                    dp[i + A[n] + B[n]] = true;
                }
            }
        }

すべての選手を列挙したら、dp 配列には ΣC[t] がとりうる値のみフラグがたった状態になっている。

最小値を求める

ΣC[t] の範囲内のすべての値について max(|SumA - ΣC[t]|, |SumB - ΣC[t]|) を計算する。この最小値が求めるべき値となる。

        // C[i] のそれぞれの値について、
        // max(|S fast - T slow|, |S slow - T fast|) を求める。
        // その中での最小値を調べる
        int ret = SumA + SumB;
        for (int i = 0; i <= SumA + SumB; i++) {
            if (dp[i]) {
                int maxdiff = max(abs(SumA - i), abs(SumB - i));
                ret = min(ret, maxdiff);
            }
        }

おしまい。自力で分かる気がしない・・・。

以下、ソース。


#include <string>
#include <vector>
#include <iostream>
#include <map>
#include <sstream>
#include <algorithm>
#include <set>
#include <numeric>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
using namespace std;

class MayTheBestPetWin {
public:
    int calc(vector <int> A, vector <int> B) {
        int SumA = 0, SumB = 0;
        int N = A.size();

        // SumA, SumB を計算
        for (int i = 0; i < N; i++) {
            SumA += A[i];
            SumB += B[i];
        }

        // 時間をキーにした dp 配列を準備
        // C[i] = A[i] + B[i] が取る得る値かどうかを格納する
        bool* dp = new bool[SumA + SumB + 1];
        memset(dp, 0, sizeof(bool) * (SumA + SumB + 1));
        dp[0] = true;
        for (int n = 0; n < N; n++) {
            for (int i = SumA + SumB; i >= 0; i--) {
                if (dp[i]) {
                    dp[i + A[n] + B[n]] = true;
                }
            }
        }

        // C[i] のそれぞれの値について、
        // max(|S fast - T slow|, |S slow - T fast|) を求める。
        // その中での最小値を調べる
        int ret = SumA + SumB;
        for (int i = 0; i <= SumA + SumB; i++) {
            if (dp[i]) {
                int maxdiff = max(abs(SumA - i), abs(SumB - i));
                ret = min(ret, maxdiff);
            }
        }

        return ret;
    }
};

2009-12-23

CutSticks (SRM456 DIV1 Medium)

| 23:40 | CutSticks (SRM456 DIV1 Medium) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - CutSticks (SRM456 DIV1 Medium) - TopCoder煮ブログ

n個の棒をC回切って、K番目に長い棒の最大長を求める問題。上位の人のソースを読んで意図を解読した。

最大C回切ることが許されているときに、長さが m 以上となる棒がK個以上あるかを求めることができる。

この長さ m を 0 と無限長の中点から始めて、m 以上か m 以下かで2分探索していく。

上位の人のソースを解読して、自分なりに書き換えてコメントいれた。ループ回数に最大回数が入ってるのは二分探索の罠を回避するため。

高レート部屋でこの定番が使えなかったのが痛いけど、見るべきは「二分探索の終了条件」です

例えば、差が1e-9以下になったらーとかいってたら、出来るだけでかいのをぶち込みますっ

仮数部が確か52bitしかないはずだから、まぁ16桁くらいまでなのかな、だとすると、答えが1e10くらいのものをぶち込まれたときに、差がそれ以下にならずに無限ループに入ったりしちゃうわけです

ただこれは入るときと入らないときがあるから落とせない、ってのが難しいところですorz

Entry is not found - chokudaiのブログ

奥が深い。。。

以下、ソース。

class CutSticks {
public:
    // 最大C回切ることが許されているときに、
    // 長さが m 以上となる棒の数を返す
    int Count(vector<int> sticks, double m, int C){
        // 戻り値
        int ret = 0;

        for(int i = 0; i < sticks.size(); i++){
            // i 本目の棒の長さを s とする
            int s = sticks[i];

            // i 本目の棒が m より小さいときは数には含まれない
            if (s < m) continue;

            // 最大何回切れるかを求める
            // s が 2 * m 程度のときは、1回切ることができる
            int count = int(s / m) - 1;

            // count の上限を残り切れる回数 C にする
            count = min(count, C);

            // 戻り値を(切れる回数+1)だけ増やす
            ret += count + 1;

            // 残り切れる回数 C を更新する
            C -= count;
        }

        return ret;
    }

    double maxKth(vector <int> sticks, int C, int K) {
        // 左端、右端を設定する
        double l = 0;
        double r = 1e9;

        // 回数制限用のカウンタ
        int count = 0;

        // 差が小さくなるか500回ループしたら終わる
        while(fabs(l - r) > 1e-10 && count < 500){
            // 中点を取得する
            double m = (l + r) / 2.0;

            // m 以上となる棒が K 個以上のときは
            // 求めるべき値は m より大きい。
            if(Count(sticks, m, C) >= K){
                l = m;
            } else {
                r = m;
            }

            count++;
        }

        return r;
    }
};

2009-12-06

NumbersAndMatches (SRM454 DIV1Comments Medium)

| 18:06 | NumbersAndMatches (SRM454 DIV1Comments Medium) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - NumbersAndMatches (SRM454 DIV1Comments Medium) - TopCoder煮ブログ

答えを見ても全く分からずに一日悩んだ。いろいろ探す過程で「今回の500は簡単」という発言をみて萎える。

今回は、桁の数を増やしながら計算するのが一番外側のループ。表に記憶するのは、1つ前の桁の過不足を含んだ数の個数。

DP

追加数と減少数の表をまず作る

del\add01234
010000
100000
200000
300000

最初に処理する桁が「2」のときは、それぞれの数に移動したときの数を記録する。

del\add01234
010100
101300
200100
301200

(0,0)は2から2に遷移するとき(つまり移動は発生しない)。(1,1)が2から1に遷移するとき(1個減らして1個追加、つまり1回の移動)。

(2,1)は2から0,6,9に遷移するとき。たとえば、2から0に遷移するには2個追加して1個減ったらいける。このままだと過不足が発生しているが、この次の桁で1個減らす遷移が発生すると、過不足は解消される。

さて、次の桁。

たとえば次の桁が「2」のときは、2から10通りの数に移る場合の移動を計算する。この移動方法を(0,0)からだけじゃなく、その他の数字がある場所全てに適用して、2桁目を変えた全パターンについて移動先の数を求める。

こんな感じで全ての桁について DP 表を更新したら、最後に K 個の対角線の数を足し合わせる。対角線というのは、マッチの移動が過不足なく行えた場所。

ソース

cafelier 先生のソースを解読してコメントつけてみた。


#include <string>
#include <vector>
#include <numeric>
#include <iostream>
using namespace std;

//   0
// 1   2
//   3
// 4   5
//   6
char* NUMBERS[] = {
    "1110111", //0
    "0010011", //1
    "1011101", //2
    "1011011", //3
    "0111010", //4
    "1101011", //5
    "1101111", //6
    "1010010", //7
    "1111111", //8
    "1111011", //9
};
int DP[18][200][200];

class NumbersAndMatches {

public:
    // a から b へ遷移するときに追加、削除するマッチの数を取得する
    void diff(int a, int b, int* add, int* del){
        for(int i = 0; i < 7; i++){
            if(NUMBERS[a][i] < NUMBERS[b][i]) *add += 1;
            else if(NUMBERS[a][i] > NUMBERS[b][i]) *del += 1;
        }
    }

    long long differentNumbers(long long N, int K) {
        // DP の領域を初期化する
        memset(DP, 0, sizeof(DP));
        DP[0][0][0] = 1;

        // 一桁ずつ処理していく
        int cnt = 0;
        while(N){
            // 処理する桁の数字を取得する
            int n = N % 10;
            N /= 10;
            cnt++;
            
            // 追加する回数、減らす回数を総当りする
            for(int i = 0; i < K; i++){
                for(int j = 0; j < K; j++){
                    // cnt 番目の数が k になるとき
                    for(int k = 0; k < 10; k++){
                        // マッチの増加数、減少数を調べる
                        int add = 0, del = 0;
                        diff(n, k, &add, &del);

                        // 移動数が上限 K を超えないときは、
                        // DP の表に記録する
                        if(add + i < K && del + j < K){
                            // 次の場所の数に n から k に移動する数を追加する
                            DP[cnt][add+i][del+j] += DP[cnt - 1][i][j];
                        }
                    }
                }
            }
        }

        // 対角線の値を K 個足し合わせる
        long long ret = 0;
        for(int i = 0; i <= K; i++){
            ret += DP[cnt][i][i];
        }
        return ret;
    }
};

2009-05-05

UnluckyIntervals (SRM438 DIV1 Easy)

| 01:10 | UnluckyIntervals (SRM438 DIV1 Easy) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - UnluckyIntervals (SRM438 DIV1 Easy) - TopCoder煮ブログ

本番 の System Test のときにだいたいの方針は理解したので書き下していくだけ。にも関わらず時間がかかる。だめだなぁ。

submit したら、Test Case 4 で落ちる。{2}, 1 というもの。前後 n 個はチェックしていたはずだが、n - 1 しか見ていなかった。境界条件で落とされた。んむー。境界条件に迷わされないために、ちょっと多めに計算しておいたほうがよいんだろうな。

ソース。無駄が多い。

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
#include <math.h>
using namespace std;

struct Unluckyness
{
  long long count;
  int number;

  Unluckyness(){}
  Unluckyness(long long c, int n): count(c), number(n){}

  bool operator<(const Unluckyness& rhs) const{
    if (count != rhs.count) return count < rhs.count;
    if (number < rhs.number) return true;
    return false;
  }
};

class UnluckyIntervals {
public:
  vector <int> getLuckiest(vector <int> luckySet, int n)
  {
    luckySet.push_back(0);
    luckySet.push_back(INT_MAX);
    sort(luckySet.begin(), luckySet.end());

    set<Unluckyness> s;

    int N = luckySet.size();
    int min = 0;
    for(int i = 0; i < N - 1; i++){
      long long num0 = (i == 0 ? INT_MIN : luckySet[i - 1]);
      long long num1 = luckySet[i];
      long long num2 = luckySet[i + 1];
      for(int j = -n; j < 0; j++){
        long long v = num1 + j;
        if (v <= min) continue;

        long long count = (v - num0) * (num1 - v) - 1LL;
        s.insert(Unluckyness(count, v));
        min = v;
      }
      s.insert(Unluckyness(0LL, num1));
      min = num1;
      for(int j = 1; j < n + 1; j++){
        long long v = num1 + j;
        if (v <= min || v >= num2) continue;

        long long count = (v - num1) * (num2 - v) - 1LL;
        if (num2 == INT_MAX) count = LLONG_MAX;
        Unluckyness u(count, v);
        s.insert(u);
        min = v;
      }
    }

    vector<int> ret;
    s.erase(s.begin());
    for(int i = 0; i < n; i++){
      ret.push_back(s.begin()->number);
      s.erase(s.begin());
    }
    return ret;
  }
}

2009-02-08

HexatridecimalSum (SRM434 DIV1 Medium)

| 12:23 | HexatridecimalSum (SRM434 DIV1 Medium) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - HexatridecimalSum (SRM434 DIV1 Medium) - TopCoder煮ブログ

リベンジ

実戦では「桁が上のものから Z に置き換えていくようにすればいい」という方針で時間切れだったので、続きを書いてみた。が、System Test に通らない。しばし考えて間違いだったと気づく。反例は、YZ,Z0,Z0,...,Z0 で k=1 のようなケース。Y を Z に置き換えても高々360増えるだけだが、0 を Z に置き換えたら36×(Z0の個数)増えて、360を超える。

つまり、数字ごとに増分を計算していかなきゃいけない。作戦を変更して書き直し。増分は double に格納していく。増分の比較ならばそこまで精度は細かくなくてよいだろう、と信じる。

やってみたらうまくいった。合計2時間ほど格闘してしまった。

C++1位の人を見てみる

C++1位の人のソースをみてあまりのシンプルさに驚く。

  1. numbers を全部足した文字列を計算する。
  2. 0~Y までのそれぞれを Z に変換したときの増分を文字列で持つ
  3. 増分の大きなもの(文字列の比較でOK)から k 個を 1. の結果に足していく。

ちょっとしたコツ:

  • 桁が違うとややこしいので、000 を各数字の最初に埋めておく。最後に先頭の 0 を取り除いて返す。

自分のソース

以下、自分のソース。

#include <string>
#include <vector>
#include <iostream>
#include <map>
#include <sstream>
#include <algorithm>
#include <set>
#include <numeric>
#include <math.h>
using namespace std;

struct diff{
  int c;
  double exp;
  // 降順にするために逆に比較
  bool operator <(const diff& rhs){
    return exp > rhs.exp;
  }
};

class HexatridecimalSum {
public:
  string maximizeSum(vector <string> numbers, int k) {
    diff E[36];
    for(int i = 0; i < 36; i++){ E[i].c = i; E[i].exp = 0; }

    // 最後に足しやすいように逆順にしておく
    int N = numbers.size();
    for(int i = 0; i < N; i++) reverse(numbers[i].begin(), numbers[i].end());

    // 増分を計算
    for(int i = 0; i < N; i++){
      string s = numbers[i];
      for(int j = 0; j < s.size(); j++){
        char c = numbers[i][j];
        int n = (c <= '9' ? c - '0' : c - 'A' + 10);
        E[n].exp += pow(36.0, j) * (35 - n);
      }
    }
    sort(E, E + 36);

    // 増分が多いものから Z に置き換えていく
    for(int i = 0; i < k; i++){
      if(E[i].exp == 0) break;
      int c = (E[i].c < 10 ? E[i].c + '0' : E[i].c - 10 + 'A');
      for(int j = 0; j < N; j++){
        for(int k = 0; k < numbers[j].size(); k++){
          if(numbers[j][k] == c) numbers[j][k] = 'Z';
        }
      }
    }

    // 36進数の加算
    string ret = "";
    int j = 0;
    int cur = 0;
    int carry = 0;
    while(true){
      cur = carry;
      bool f = false;
      for(int i = 0; i < N; i++){
        int s = numbers[i].size();
        if(j >= s) continue;
        f = true;
        int c = numbers[i][j];
        if(c <= '9') cur += c - '0';
        else cur += c - 'A' + 10;
      }
      if(!f && cur == 0) break;
      carry = cur / 36;
      cur %= 36;
      if(cur < 10) ret += (char)(cur + '0');
      else ret += (char)(cur - 10 + 'A');
      j++;
    }

    // 最後に反転して返す
    reverse(ret.begin(), ret.end());
    return ret;
  }
};

FindingSquareInTable (SRM434 DIV1 Easy)

| 13:53 | FindingSquareInTable (SRM434 DIV1 Easy) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - FindingSquareInTable (SRM434 DIV1 Easy) - TopCoder煮ブログ

本番では混乱したまま書き始めてうまく行かなかった問題。開始座標と増分の4重ループで素直に書けた。なんと…。

上位の人のソースを読んだ感想。

  • for のループを横に連ねてタブの階層を減らしたほうがよさそうだ。
  • perfect square かの判別は (int)sqrt(1.0*n) を2乗して n と比較すればよい。
    • 自分は sqrt して modf したけど一時変数がいるのがかっこ悪いし誤差が怖い。

以下、自分のソース。

class FindingSquareInTable {
public:
  int findMaximalSquare(vector <string> table) {
    int N = table.size();
    int M = table[0].size();

    int ret = -1;
    for(int x0 = 0; x0 < M; x0++){
      for(int y0 = 0; y0 < N; y0++){
        for(int xd = -9; xd <= 9; xd++){
          for(int yd = -9; yd <= 9; yd++){
            int x = x0;
            int y = y0;

            int num = 0;
            while(x >= 0 && y >= 0 && x < M && y < N){
              num *= 10;
              num += table[y][x] - '0';
              double tmp;
              if(modf(sqrt((double)num), &tmp) == 0.0){
                ret = max(ret, num);
              }

              if(xd == 0 && yd == 0) break;
              x += xd; y+= yd;
            }
          }
        }
      }
    }
    return ret;
  }
};

nishiohirokazunishiohirokazu2009/02/08 18:43mod系がダメな理由の一つはnが0になりうるって点かと。

2009-02-07

MagicWords (SRM433 DIV2 Medium)

| 00:29 | MagicWords (SRM433 DIV2 Medium) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - MagicWords (SRM433 DIV2 Medium) - TopCoder煮ブログ

解けなかった問題。あとで確認したら TLE だった。

本番で Submit したところの

if(s == s2.substr(i, N)) count++; 

を、次のように修正したら通った。

if(strncmp(s.c_str(), s2.c_str() + i, N) == 0) count++; 

要約:substr はコスト高いよね

GeralynGeralyn2011/07/10 04:08Your article perfectly shows what I needed to know, thakns!

rxpneerxpnee2011/07/10 16:28L6LbkJ <a href="http://dkbiqormanar.com/">dkbiqormanar</a>

znawihcgznawihcg2011/07/11 19:28rd9Sbw , [url=http://dmalqhfjjmxd.com/]dmalqhfjjmxd[/url], [link=http://tjjlgywwhmkh.com/]tjjlgywwhmkh[/link], http://tusvscmndbip.com/

eveflytxeveflytx2011/07/12 17:29VCVOJ4 <a href="http://rohqyiocswre.com/">rohqyiocswre</a>

ltquhxgltquhxg2011/07/12 22:02dtGtLV , [url=http://dzarhwolpmbn.com/]dzarhwolpmbn[/url], [link=http://gnnspdoegvwz.com/]gnnspdoegvwz[/link], http://ltsooreekkfx.com/

2008-12-02

LocateTreasure (SRM427 Medium)

| 01:16 | LocateTreasure (SRM427 Medium) - TopCoder煮ブログ を含むブックマーク はてなブックマーク - LocateTreasure (SRM427 Medium) - TopCoder煮ブログ

できなかった理由

  • dig を再帰的に適用していなかった
  • 周期が何回あらわれるかを求めるところで、周期に至るまでの初期ごにょごにょするところで、ちょっとしたインデックスの計算ミス
  • Kの数が周期運動に入るよりも前のときの扱いができていなかった

結局3箇所ぐらいできていなかったわけで、まだまだ先は長いことがよく分かった。

C++ 1位の人のソースを見たが、方針自体は同じだった。

CynthiaCynthia2012/07/12 13:44I'm impressed by your writing. Are you a profsesoinal or just very knowledgeable?

zntishlzntishl2012/07/12 23:22ffVL8d <a href="http://qztfougzgfzq.com/">qztfougzgfzq</a>

anfabjanfabj2012/07/15 02:59YAp5V0 <a href="http://rezjmaqxnqov.com/">rezjmaqxnqov</a>

jypuczajypucza2012/07/15 10:42S5trWw , [url=http://qkujzogwyobf.com/]qkujzogwyobf[/url], [link=http://plidwzjoiafl.com/]plidwzjoiafl[/link], http://gfqueuecdrab.com/