問題概要
空間を飛び回る粒子があります。2つの粒子がぶつかると、以下の3つのうちどれかが起きます。
- 最初の粒子が消滅する
- 2つ目の粒子が消滅する
- 何も起こらない
粒子の数は50個以下です。任意のペアの粒子が衝突した場合、上の1-3のうちどれが起こるかという情報が与えられます。その情報を元にランダムな回数・順番で粒子同士が衝突したとき、最終的に残る粒子の数の最小値を求めよ。
方針
強連結成分(SCC)分解して、DAGにして、DAGの根の数が答え。強連結成分分解は、DFSを二回やる方法が有名ですが、この問題では節点数が小さいのでワーシャルフロイド法を利用したやり方でもできます。
- 粒子Aが粒子Bを消すことができるとき、AからBへ有向枝をはる。
- ワーシャルフロイドで、1.の推移閉包を求める。
- i->jパス、j->iパスが同時に存在するとき、閉路と考える。これを利用して、SCCにグルーピングしていく。
- SCCを1つの節点とみなして、枝をはり直してDAGにする。
- 各接点の入次数を数える。
まあソース見た方が早いです。DFS2回バージョンは直感的に理解するのが難しいですが、こっちのワーシャルフロイドバージョンは直感的につかみやすいですねー。実装量も少ないので、計算量に余裕がある場合はこっちのほうがいいかもしれません。
ソースコード
bool win[50][50];
int id[50];
int d[50];
class StrangeParticles {
public:
int remain(vector<string> interacts) {
int N = interacts.size();
memset(win, 0, sizeof(win));
REP(i, N) REP(j, N) if (i == j || interacts[i][j] == '-') win[i][j] = 1;
REP(k, N) REP(i, N) REP(j, N) win[i][j] |= win[i][k] & win[k][j];
memset(id, -1, sizeof(id));
int p = 0;
REP(i, N) {
if (id[i] == -1) {
FOR (j, i, N) if (win[i][j] & win[j][i]) id[j] = p;
++p;
}
}
memset(d, 0, sizeof(d));
REP(i, N) REP(j, N) if (win[i][j]) {
if (id[i] != id[j])
d[id[j]]++;
}
int ret = 0;
REP(i, p) if (d[i] == 0) ++ret;
return ret;
}
};
0 件のコメント:
コメントを投稿