Search on the blog

2012年5月8日火曜日

Google Code Jam 2012 1B Equal Sums

問題
 N個の要素をもつ正の整数の集合S := {S1, S2, ... SN}が与えられる。Sの異なる任意の2つの部分集合TとUを考える。Tの要素の総和とUの要素の総和が同じになる場合、T、Uの要素を出力せよ。そのようなTとUが作れない場合は、"impossible"と出力せよ。

方針
 smallは、N=20でSの要素が105程度だったから、DPで解いた。largeは、N=500でSの要素が最大で1012だったので、DPでは無理。乱択アルゴリズムを使ってる人が多かった。
 N=500だけど、最初の50個だけ見る。このとき作れる部分集合の個数は250 > 500 * 10 12なので、鳩ノ巣原理から確実に衝突するペアが存在する。仮に106個部分集合を作ったとすると、部分集合からペア2つを選ぶ選び方は、1012/2通り、一つのペアにおいて衝突が起こる確率は低く見積もっても1/1012なので、106個の部分集合のなかで衝突するようなペアの数の期待値は、1012/2 * 1/1012でほぼ1くらいのオーダーになる。ということで適当に部分集合を生成してその和をmapで管理していれば100万個くらいループまわせばそのうち衝突するでしょう。という方針でいく。

ソースコード
 setやmapは要素数が100万くらいになると急激に遅くなる場合があるので、今回のようなケースではハッシュを使ったmapを使った方がいいと思います。以下では、boostのunordered_mapを使って実装しています。
using namespace std;

#define REP(i, n) for(int i=0; i<(int)(n); i++)
#define FOR(i, s, e) for (int i = (int)(s); i < (int)(e); i++)

int N;
long long x[500];

void printNums(unsigned long long use) {
    bool fst = true;

    REP (i, N) {
        if (use & 1LL << i) {
            if (fst)
                fst = false;
            else
                cout << " ";
            cout << x[i];
        }
    }
    cout << endl;
}


void solve() {
    unsigned long long use = 0;
    boost::unordered_map<long long, unsigned long long> vis;

    N = min(N, 50);
    unsigned long long use2 = 0;
    for (;;) {
        use = use * 23 + 17542197;
        long long sum = 0;
        REP(i, N) if (use & 1LL<<i) sum += x[i];

        if (sum && vis.count(sum)) {
            use2 = vis[sum];
            break;
        }
        vis[sum] = use;
    }

    printNums(use);
    printNums(use2);
}

#define SUBMIT
int main() {
#ifdef SUBMIT
    freopen("./test.in", "r", stdin);
    freopen("./test.out", "w", stdout);
#endif

    int T;
    cin >> T;
    REP(t, T) {
        cout << "Case #" << t+1 << ":" << endl;
        cerr << "Case #" << t+1 << ":" << endl;

        cin >> N;
        REP(i, N) cin >> x[i];
        solve();
    }

#ifdef SUBMIT
    fflush(stdout);
    cerr << "all done!" << endl;
#endif
    return 0;
}

0 件のコメント:

コメントを投稿