Redaksiya
Problem Author: | Valerii Kovnatskyi |
Prepared by: | Valerii Kovnatskyi |
Editorial by: | Valerii Kovnatskyi |
Let's consider a vertex . Let its final color be . It is claimed that the eruption of geyser must occur after the eruptions of all geysers for which the following conditions hold:
The distance from to does not exceed (in other words, is within the coloring zone of )
It is not difficult to understand that this set of conditions is not only necessary but also sufficient for the color of vertex to be exactly .
Group 2:
For each geyser , we iterate through all vertices in the coloring zone. If and , we draw an edge from to , which will denote the condition that the eruption of geyser must occur before the eruption of geyser . This will work in a number of operations equal to (the same number of edges will be in the resulting graph). Such a set of conditions will be identical to the set described above, applied for each vertex.
Now, we will use the topological sorting algorithm to obtain the required order of vertices. The time complexity of the algorithm is , therefore the entire solution works in . An example implementation:
void solve() { int n; cin >> n; vector<int> d(n), c(n); cin >> d >> c; for (int i = 0; i < n; i++) { if (c[i] != -1) c[i] -= 1; } vector<vector<int>> g(n); for (int i = 0; i < n - 1; i++) { int v, u; cin >> v >> u; v -= 1, u -= 1; g[v].push_back(u); g[u].push_back(v); } vector<vector<int>> h(n); for (int v = 0; v < n; v++) { if (c[v] != -1 && c[v] != v) { h[v].push_back(c[v]); } for (int u : g[v]) { if (c[u] != -1 && c[u] != v) { h[v].push_back(c[u]); } } } vector<int> order; vector<bool> used(n, false); function<void(int)> topsort = [&] (int v) { used[v] = true; for (int u : h[v]) { if (used[u]) continue; topsort(u); } order.push_back(v); }; for (int v = 0; v < n; v++) { if (!used[v]) topsort(v); } reverse(all(order)); for (int el : order) cout << el + 1 << ' '; }
For a complete solution, we will use the idea of centroid decomposition. Specifically, we will use it to maintain the progress of the topological sorting algorithm without explicitly constructing the graph in advance.
Let's say we are currently at vertex . We know that we have edges to those vertices for which there exists a vertex such that and the length of the path from to does not exceed . Let's iterate through the centroid ancestors of vertex , let the current one be vertex . We will check such centroid children of vertex for which , that is, . It is obvious that from this equality it follows that , but we can also say that if lies on the path between and , then . Therefore, if we consider any suitable vertex , at the moment when we iterate through the centroid children of the least common centroid ancestor of vertices and (which, as is known, lies on the path from to ), we will definitely check vertex . Thus, by iterating through the centroid ancestors and centroid children in this way, we will check all the necessary vertices.
We will store two arrays of pairs for each vertex; the first will contain the indices of the centroid ancestors and the distances to them, and the second will contain the indices of the centroid children and the distances to them, with the latter needing to be sorted by distances so that suitable centroid children are stored in some prefix. Both of these arrays can be filled during the construction of the centroid decomposition, either in time by explicitly sorting the arrays by distances, or in using breadth-first search.
Now we can perform the topological sorting as described above. We need to apply one optimization to achieve acceptable asymptotic behavior — if we have considered a centroid child of centroid ancestor , then there is no point in reconsidering it further, since its color has already been used. Therefore, we will store the length of the already considered prefix of the array for each centroid. Since the total size of the stored information is and we consider each element at most once, the final time complexity will be .
void solve() { int n; cin >> n; vector<int> d(n), c(n); cin >> d >> c; for (int i = 0; i < n; i++) { if (c[i] != -1) c[i] -= 1; } vector<vector<int>> g(n); for (int i = 0; i < n - 1; i++) { int v, u; cin >> v >> u; v -= 1, u -= 1; g[v].push_back(u); g[u].push_back(v); } vector<bool> disabled(n, false); vector<int> sz(n); function<void(int, int)> calc_sz = [&] (int v, int pr) { sz[v] = 1; for (int u : g[v]) { if (disabled[u] || u == pr) continue; calc_sz(u, v); sz[v] += sz[u]; } }; function<int(int, int, int)> find_centroid = [&] (int v, int pr, int root) { for (int u : g[v]) { if (disabled[u] || u == pr) continue; if (sz[u] * 2 > sz[root]) return find_centroid(u, v, root); } return v; }; struct centroid_data { int v, dist; }; vector<vector<centroid_data>> centroid_parents(n), centroid_children(n); function<void(int)> centroid_decomposition = [&] (int root) { calc_sz(root, -1); int centroid = find_centroid(root, -1, root); struct bfs_data { int v, pr, dist; }; queue<bfs_data> q; q.push({centroid, -1, 0}); while (!q.empty()) { bfs_data curr = q.front(); q.pop(); centroid_children[centroid].push_back({curr.v, curr.dist}); centroid_parents[curr.v].push_back({centroid, curr.dist}); for (int u : g[curr.v]) { if (disabled[u] || u == curr.pr) continue; q.push({u, curr.v, curr.dist + 1}); } } disabled[centroid] = true; for (int v : g[centroid]) { if (!disabled[v]) { centroid_decomposition(v); } } }; centroid_decomposition(0); vector<int> checked(n, 0); vector<bool> used(n, false); vector<int> order; function<void(int)> topsort = [&] (int v) { used[v] = true; for (auto pr : centroid_parents[v]) { while (checked[pr.v] < len(centroid_children[pr.v])) { centroid_data curr = centroid_children[pr.v][checked[pr.v]]; if (curr.dist > d[v] - pr.dist) break; checked[pr.v]++; if (used[c[curr.v]]) continue; if (c[curr.v] == -1) continue; topsort(c[curr.v]); } } order.push_back(v); }; for (int v = 0; v < n; v++) { if (!used[v]) topsort(v); } reverse(all(order)); for (int el : order) cout << el + 1 << ' '; }