
配点: 450 点 / 実行時間制限: 3 sec / メモリ制限: 1024 MB / Difficulty: 1222 / NoviSteps: 1D
問題概要
頂点
辺の自己ループを持たない連結無向グラフが与えられる。辺
は頂点
と頂点
を双方向に結んでおり、ラベル
がつけられている。
頂点 から頂点
までの単純パスのうち、パスに含まれる辺につけられたラベルの総ビット単位
としてあり得る最小値を求めよ。
制約
- 入力される値は全て整数
考察
ABC396-D で似たような名前の問題は出題されているが、今回は頂点数 の制約が大きいため、単純パスの全列挙はできない。
そこで、bit演算系の問題の解法の典型「bitごとに考える」を、この場合も適用することを考える。
例として、以下のようなグラフを考える。
のケース
初期値として、総 が
である経路(赤のパス)を考える。
ここから最上位bitを にできるような経路が存在するか判定する。このケースでは、緑色の経路を辿れば、総
が
とすることができる。
このようにして、 (辺のラベルとしてあり得る最大値)の上位bitから順に
とできるかどうかを試していく。この判定には BFS / DFS あるいは Union-Find を用いることができる。
現在注目している bit目について、
にできないならばそのbitは
で値が確定する。
また、既に で値が確定したbitについて、判定の過程でそのbitが
になるような辺は通過することができない。
以上を踏まえて実装すれば、BFS では 、Union-Find では
(
はアッカーマン関数の逆関数) の計算量で答えを求めることができる。
Union-Findの方がコードを書く量は少ないので速そう。実装例ではACLのDSU
を用いている。
実装例
BFS の利用
#include <bits/stdc++.h> using namespace std; #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) #define rrep(i, start, end) for (auto i = (start); (i) >= (end); (i)--) template <class T> using Graph = vector<vector<T>>; // ======================================== // struct Edge { int to; int cost; }; int main() { int N, M; cin >> N >> M; Graph<Edge> G(N); rep(i, 0, M) { int u, v, w; cin >> u >> v >> w; u--, v--; G[u].push_back({v, w}); G[v].push_back({u, w}); } auto bfs = [&](int limit) -> bool { vector<bool> visited(N, false); queue<int> que; que.push(0); visited[0] = true; while (!que.empty()) { int next = que.front(); que.pop(); if (next == N - 1) { return true; } for (const auto &e : G[next]) { if (!visited[e.to]) { if ((e.cost | limit) == limit) { visited[e.to] = true; que.push(e.to); } } } } return false; }; int ans = (1LL << 30) - 1; rrep(bit, 29, 0) { if (ans & (1LL << bit)) { int temp = ans & ~(1 << bit); if (bfs(temp)) { ans = temp; } } } cout << ans << endl; return 0; }
Union-Find の利用
#include <bits/stdc++.h> using namespace std; #if __has_include(<atcoder/all>) #include <atcoder/all> using namespace atcoder; #endif #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) #define rrep(i, start, end) for (auto i = (start); (i) >= (end); (i)--) // ======================================== // struct Edge { int from; int to; int cost; }; int main() { int N, M; cin >> N >> M; vector<Edge> edges(M); rep(i, 0, M) { cin >> edges[i].from >> edges[i].to >> edges[i].cost; edges[i].from--; edges[i].to--; } int ans = (1LL << 30) - 1; rrep(bit, 29, 0) { if (ans & (1LL << bit)) { int temp = ans & ~(1 << bit); dsu dsu(N); for (auto &&e : edges) { if ((e.cost | temp) == temp) { dsu.merge(e.from, e.to); } } if (dsu.same(0, N - 1)) { ans = temp; } } } cout << ans << endl; return 0; }
実装時間: 40分
コメント
「bitごとに見る」解法を最短経路問題に絡めた問題は初めて見たかもしれない。また、競プロの文脈でのbit演算といえば XOR が大半だったので、OR に関する問題は新鮮だった。