LoginSignup
0

More than 1 year has passed since last update.

posted at

topcoder SRM 802 Div1 Medium BestEvenSplit

topcoder SRM 802 Div1 Medium BestEvenSplitをビットマスクの列挙で解いてみた。

問題

  • Nは偶数で2から30まで
  • N人を同人数の二つのグループにわける
  • 友達のペアが与えられる
  • 友達が同じグループになる個数を最大化するき、何通りのわけかたがありえるか求める

方針

30ビットは巨大だが、そのうちの15人固定なので、もしかしてビットで全部列挙できるのでは? と思ってやってみた。
前処理として、自分の友達をビットマスクで生成しておく。それにより、自分が所属するグループに友達が何人いるかビットでわかる。

実装

Nビットのうち、ちょうどKビット立っている集合を列挙する方法は蟻本p.144と、けんちょん氏のビット演算 (bit 演算) の使い方を総特集!に載っている。

inline int next_combination(int sub) {
    int x = sub & -sub, y = sub + x;
    return (((sub & ~y) / x) >> 1) | y;
}

これでやってみたところ、30ビット(10億通り)のうち15個ビットが立つのは1.5億個くらいであった。しかしループだけで4秒以上かかってしまう。(この問題は特別に制限時間が3秒に拡大されている)
その後、立っているビットと立っていないビットの数は同じなので、反転したものを使えば、半分だけ列挙すればよいことに気づいた。上の next_combination は昇順に列挙できるので、ビットマスクの値が、反転したビットマスクの値未満の間だけ列挙すればよい。すなわちビットマスクの値が2^(N-1)までを列挙する。そうすると半分の77,558,760回の処理となり、3秒でも収まるようになった。

class BestEvenSplit {
public:
    int count(int N, vector <int> X, vector <int> Y) {
        vector<int> friends(N);
        for (int i = 0; i < (int)X.size(); ++i) {
            friends[X[i]] |= 1 << Y[i];
            friends[Y[i]] |= 1 << X[i];
        }
        int ans = 0, max_count = 0;
        for (int comb = (1 << N / 2) - 1; comb < (1 << (N - 1)); comb = next_combination(comb)) {
            int cnt = 0;
            for (int i = 0; i < N; ++i) {
                // この人が所属するグループのビットマスク(combまたは~comb)を使う
                cnt += __builtin_popcount(friends[i] & ((comb & (1 << i)) ? comb : ~comb));
            }
            ans += cnt == max_count;
            if (cnt > max_count) {
                // マッチした数が最大値を超えていたら答えをリセットする
                max_count = cnt;
                ans = 1;
            }
        }
        return ans;
    }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0