H - Art Gallery on Graph 解説 by en_translator
Let us first rephrase the condition of “guarded vertex”,
- there is at least one guard \(i\) such that vertex \(v\) and vertex \(p_i\) are distant by at most \(h_i\),
in an understandable way. We can rephrase as follows:
- A guard can move from the current vertex to an adjacent one by consuming a health of \(1\). Once the health reaches \(0\), the guard cannot move anymore.
A vertex is said to be “guarded” if and only if at least one guard can reach \(v\).
Such a rephrasing makes it easier to come up with a solution.
Let \(d_i\) be the maximum health of a guard who reaches vertex \(i\) (or \(-1\) if no one can reach there). If we can find all \(d_1, d_2, \dots, d_N\), then this problem can be solved.
In fact, \(d_1, d_2, \dots, d_N\) can be calculated by the following algorithm:
- Let \(x_1, x_2, \dots, x_N\) be an array that stores preliminary \(d_i\). If \(p_i\) has a guard, \(x_i\) is initialized with the guard’s initial health; otherwise, it is initialized with \(-1\).
- Repeat the following procedure \(N\) times to determine the values of \(d_{i}\) one by one.
- Let \(v\) be the vertex, whose \(d_i\) is undetermined, with the maximum \(x_i\).
- First, let \(d_v = x_v\) to determine the value \(d_v\).
- Then, for each vertex \(u\) adjacent to \(v\),
- if \(d_u\) is undetermined, then replace \(x_u\) with \(\min(x_u, x_v - 1)\).
This is justified by induction. (We omit the proof; it can be shown in the same way as the justification of Dijkstra’s algorithm, so if you do not know how, first learn Dijkstra’s algorithm.)
A naive implementation of this algorithm costs a total of \(\mathrm{O}(N^2)\) time because finding the vertex with minimum \(x_i\) costs \(\mathrm{O}(N)\) time; however, we can use a data structure like a heap or a segment tree to manage vertices with minimum \(x_i\) to reduce the complexity to \(\mathrm{O}((N + M) \log (N + M))\) time, which is fast enough to get AC (accepted). (Since the edge weights of the graph are all \(1\), it can be further reduced to \(\mathrm{O}(N + M)\).)
By the way, this algorithm is equivalent to a shortest-path problem, with the signs of costs flipped. Also, the algorithm above is similar to Dijkstra’s algorithm, again, with the signs flipped. With this fact, one can boil this problem down to a shortest-path problem by constructing an appropriate graph.
- Sample code (C++)
#include <iostream>
#include <queue>
#include <utility>
#include <vector>
using namespace std;
int main() {
cin.tie(nullptr);
ios::sync_with_stdio(false);
int N, M, K;
cin >> N >> M >> K;
vector<vector<int>> g(N);
for (int i = 0; i < M; i++) {
int a, b;
cin >> a >> b, --a, --b;
g[a].push_back(b), g[b].push_back(a);
}
vector<int> p(K), h(K);
for (int i = 0; i < K; i++) cin >> p[i] >> h[i], p[i]--;
vector<int> d(N, -1);
priority_queue<pair<int, int>> Q;
auto add = [&](int v, int x) {
if (d[v] < x) Q.emplace(d[v] = x, v);
};
for (int i = 0; i < K; i++) add(p[i], h[i]);
while (Q.size()) {
auto [x, v] = Q.top();
Q.pop();
if (d[v] != x) continue;
for (auto& u : g[v]) add(u, d[v] - 1);
}
vector<int> ans;
for (int i = 0; i < N; i++)
if (d[i] >= 0) ans.push_back(i + 1);
cout << ans.size() << "\n";
for (int i = 0; i < (int)ans.size(); i++)
cout << ans[i] << " \n"[i + 1 == (int)ans.size()];
}
投稿日時:
最終更新: