重み付き Union-Find が有効活用できる問題!
問題概要
個の変数 の値を決定したい。
これらの値を決定するための 本の制約条件がある。このうち 個めの情報は 3 つの値 によって与えられ、
であるという制約条件を表す。
これら 本の制約条件には誤りが含まれる可能性がある。 本の制約条件をすべて満たす解が存在するかどうかを判定せよ。
制約
解法 (1):重み付き Union-Find
重み付き Union-Find を知っていれば、それを使うだけで解ける問題だとわかる!!
重み付き Union-Find は、Union-Find の各ノードに重みを持たせたデータ構造であり、以下のクエリ処理をサポートできる。なお、この表では Union-Find のノード x
の重みを と表記している。
クエリ | 処理内容 |
---|---|
merge(x, y, w) |
となるように x と y を併合する |
issame(x, y) |
ノード x , y が同じグループにいるかどうかを判定する |
diff(x, y) |
ノード x , y が同じグループにいるとき、 の値を返す |
詳細は次の記事にて。
具体的には、 に対して、次の処理を実行することで解ける。
- 制約条件 がそれまでの条件に矛盾しないかを確かめる
issame(L[i], R[i]) == False
のときは、この 2 値には何の制約もないので問題ないissame(L[i], R[i]) == True
で、diff(L[i], R[i]) != D[i]
の場合は「整合しない」と判定できる
- 矛盾しない場合は、
merge(L[i], R[i], D[i])
を実行する
計算量は 。
コード
#include <bits/stdc++.h> using namespace std; // 重み付き Union-Find template<class Abel> struct UnionFind { const Abel UNITY_SUM = 0; // ゼロに相当する値を入れる vector<int> par; vector<Abel> diff_weight; UnionFind() { } UnionFind(int n) : par(n, -1), diff_weight(n, UNITY_SUM) {} int root(int x) { if (par[x] < 0) return x; else { int r = root(par[x]); diff_weight[x] += diff_weight[par[x]]; return par[x] = r; } } Abel calc_weight(int x) { root(x); return diff_weight[x]; } bool issame(int x, int y) { return root(x) == root(y); } bool merge(int x, int y, Abel w = 0) { w += calc_weight(x); w -= calc_weight(y); x = root(x); y = root(y); if (x == y) return false; if (par[x] > par[y]) swap(x, y), w = -w; // merge technique par[x] += par[y]; par[y] = x; diff_weight[y] = w; return true; } Abel diff(int x, int y) { return calc_weight(y) - calc_weight(x); } int size(int x) { return -par[root(x)]; } }; int main() { int N, M; cin >> N >> M; // 重み付き Union-Find を宣言 UnionFind<long long> uf(N); // 制約条件を順に処理する bool res = true; for (int i = 0; i < M; ++i) { int L, R; long long D; cin >> L >> R >> D; --L, --R; if (uf.issame(L, R) && uf.diff(L, R) != D) { res = false; } else { uf.merge(L, R, D); } } cout << (res ? "Yes" : "No") << endl; }
解法 (2):BFS や DFS で値を埋めて整合性を確認する
二部グラフ判定問題を BFS / DFS で実行する方法に馴染みのある人なら、それを応用することで解けることがピンと来ると思う!
まず、 本の制約条件をそれぞれ「辺」とみなしたグラフを考えることにする。このグラフ上で BFS / DFS を実行する。
頂点 を始点とした BFS / DFS を開始することを考える。このとき としておく。 は相対的な関係なので、始点 に対する の値は好きに決めて良い。
そして、探索中に辺 を辿ったときに、次の作業を実行する。
- の値がまだ決まっていないならば、辺 に関する制約条件から、 の値を決定する
- の値がすでに決まっているならば、その値が辺 に関する制約条件と矛盾しないかどうかを確認する
BFS でも DFS でも同様にできる。計算量は となる。
コード
実装上は、グラフに辺 (重み ) を追加するときに、辺 (重み ) を一緒に追加すると楽になる。
#include <bits/stdc++.h> using namespace std; using Edge = pair<int, long long>; // 重み付き辺 (終点, 重み) using Graph = vector<vector<Edge>>; // グラフ int main() { // 入力 int N, M; cin >> N >> M; Graph G(N); for (int i = 0; i < M; ++i) { int L, R; long long D; cin >> L >> R >> D; --L, --R; G[L].emplace_back(R, D); G[R].emplace_back(L, -D); } // BFS bool res = true; vector<bool> seen(N, false); vector<long long> x(N, -1); queue<int> que; for (int s = 0; s < N; ++s) { if (seen[s]) continue; // 頂点 s から探索開始 seen[s] = true; x[s] = 0; que.push(s); while (!que.empty()) { int v = que.front(); que.pop(); for (Edge e : G[v]) { if (seen[e.first]) { // 探索済みの場合は整合性を確認する if (x[e.first] - x[v] != e.second) { res = false; } } else { // 探索済みでない場合は x の値を定める seen[e.first] = true; x[e.first] = x[v] + e.second; que.push(e.first); } } } } cout << (res ? "Yes" : "No") << endl; }