Hướng dẫn giải của Christmas security
Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người làm lời giải.
Nộp code mẫu trước khi tự giải được bài tập là một hành vi có thể bị ban.
Tác giả:
Đề bài gốc: BOI 2019 - Valley
Tóm tắt đề: Cho một cây đỉnh, các cạnh có trọng số. Cây có 1 đỉnh thoát và đỉnh đặc biệt. Bạn cần trả lời câu hỏi có dạng:
- Nếu bỏ cạnh khỏi cây, thì có đường đi từ đến hay không?
- Nếu không có, đỉnh đặc biệt gần nhất có khoảng cách là bao nhiêu?
Subtask 1: ~1 \le N ≤ 100, 1 ≤ Q ≤ 10\,000~, đồ thị có dạng 1-2-3-...-N
Bỏ một cạnh bất kì sẽ tách đồ thị thành hai phía: 1-2-...-u và (u+1)-(u+2)-...N. Ta có thể dễ dàng kiểm tra và và có thuộc cùng một phía hay không. Nếu không, ta sẽ duyệt qua phần đồ thị chứa để xác định khoảng cách đỉnh đặc biệt gần nhất với .
Subtask 2: ~1 ≤ N ≤ 1\,000, 1 ≤ Q ≤ 1\,000~
Với subtask này, ta thực hiện những gì đề bài yêu cầu. Với mỗi câu hỏi, ta thực hiện BFS từ để xác định và có liên thông với nhau hay không, nếu không, duyệt danh sách các đỉnh đặc biệt xem khoảng cách ngắn nhất đến là bao nhiêu.
Subtask 3: ~1 ≤ N ≤ 100\,000, 1 ≤ Q ≤ 100\,000~, và mọi đỉnh đều là đỉnh đặc biệt
Với subtask này, do mọi đỉnh đều là đỉnh đặc biệt, nên nếu và không liên thông, ta chỉ cần in ra khoảng cách . Việc còn lại là ta cần tìm cách kiểm tra tính liên thông giữa và đủ nhanh để đáp ứng giới hạn của subtask này.
Chọn là gốc của cây, ta tiến hành DFS cây từ , ta dùng kĩ thuật đường đi Euler trên cây để lưu lại các thời điểm bắt đầu thăm ~t_{in}~ và thăm xong ~t_{out}~ của mỗi đỉnh trên cây. Từ đó ta có thể kiểm tra xem cạnh thứ có nằm trên đường đi từ đến hay không với độ phức tạp là . Ta có thể kiểm tra bằng cách sử dụng tính chất sau: Nếu là con của , thì điều kiện sau thỏa mãn:
Subtask 4: ~1 ≤ N ≤ 100\,000, 1 ≤ Q ≤ 100\,000~.
Với subtask 3, ta đã biết cách kiểm tra trường hợp in ra failed. Đến subtask 4, ta sẽ tìm cách giải quyết trường hợp còn lại của bài toán.
Xét truy vấn , gọi là đỉnh thấp hơn thuộc cạnh . Ta cần tìm đỉnh đặc biệt thuộc cây con gốc sao cho có khoảng cách đến là ngắn nhất.
Gọi là tổ tiên chung thấp nhất của hai đỉnh và . Khi đó, độ dài đường đi từ đến được xác định bằng công thức.
Để tìm đỉnh đặc biệt gần nhất, ta gọi là đỉnh cần tìm, giả sử ta biết là . Khi đó, đáp án của truy vấn là
Có thể thấy rằng cụm ~\min\limits_{v \ \in \text{ subtree}(w)}dist_{S}[v] - 2\times dist_{S}[w]~ không phụ thuộc vào và có thể được chuẩn bị trước, ta gọi sao cho:
Khi đó có thể được chuẩn bị trước bằng quy hoạch động như sau:
- DFS để tìm ~\min\limits_{v \ \in \text{ subtree}(w)}dist_{S}[v]~ và lưu vào . Độ phức tạp .
- Với mỗi đỉnh , thêm ~- 2\times dist_{S}[w]~ vào ~best[w]~
Như vậy, nếu biết trước là đỉnh "cao nhất" của đường đi từ đến đỉnh đặc biệt, khoảng cách của đỉnh đặc biệt gần nhất sẽ là . Đáp án cho truy vấn trở thành:
với thuộc tập đỉnh nằm trên đường đi từ đến .
Ta có thể tính ~\min\limits_{w} best[w]~ với từ đến trong bằng kĩ thuật binary lifting, dùng cấu trúc dữ liệu bảng thưa. Gọi là tổ tiên thứ của , và là nhỏ nhất với nằm trên đường đi từ đến tổ tiên thứ của . Ta có
Như vậy, tóm tắt lời giải như sau:
- DFS để tính các giá trị: ~t_{in}, t_{out}, best, par[u][0]~ trong
- Chuẩn bị bảng thưa và trong
- Trả lời truy vấn, mỗi truy vấn mất . Tổng cộng .
Như vậy độ phức tạp của lời giải này là .
Code tham khảo
#include <iostream> #include <vector> using namespace std; typedef long long ll; const int maxn = 200005; const int maxlg = 20; const ll inf = 1LL << 60; vector<pair<int, ll>> adj [maxn]; ll height [maxn]; int lvl [maxn]; ll dp [maxn]; bool store [maxn]; int start_time [maxn]; int end_time [maxn]; int cur_time; void build_dp (int vertex, int parent, ll cur_height) { cur_time++; start_time[vertex] = cur_time; height[vertex] = cur_height; lvl[vertex] = lvl[parent] + 1; for (pair<int, ll> nxt : adj[vertex]) { if (nxt.first != parent) { build_dp(nxt.first, vertex, cur_height + nxt.second); } } if (store[vertex]) { dp[vertex] = height[vertex]; } else { dp[vertex] = inf; for (pair<int, ll> nxt : adj[vertex]) { if (nxt.first != parent) { dp[vertex] = min(dp[vertex], dp[nxt.first]); } } } end_time[vertex] = cur_time; } bool is_parent (int u, int par) { return start_time[par] <= start_time[u] && end_time[u] <= end_time[par]; } int jmp [maxn][maxlg]; ll ans_jmp [maxn][maxlg]; void build_jmp (int vertex, int parent) { jmp[vertex][0] = parent; if (dp[vertex] == inf) { ans_jmp[vertex][0] = inf; } else { ans_jmp[vertex][0] = dp[vertex] - 2 * height[vertex]; } for (int i = 1; i < maxlg; i++) { jmp[vertex][i] = jmp[jmp[vertex][i - 1]][i - 1]; ans_jmp[vertex][i] = min(ans_jmp[vertex][i - 1], ans_jmp[jmp[vertex][i - 1]][i - 1]); } for (pair<int, ll> nxt : adj[vertex]) { if (nxt.first != parent) { build_jmp(nxt.first, vertex); } } } ll query (int vertex, int forb) { if (!is_parent(vertex, forb)) { return -1; } ll cur_h = height[vertex]; int diff = lvl[vertex] - lvl[forb]; ll ans = inf; for (int i = maxlg - 1; i >= 0; i--) { if (diff & 1 << i) { ans = min(ans, ans_jmp[vertex][i]); vertex = jmp[vertex][i]; } } ans = min(ans, ans_jmp[vertex][0]); if (ans != inf) { ans += cur_h; } return ans; } int main () { int vertexc, storec, queryc, root; cin >> vertexc >> storec >> queryc >> root; vector<pair<int, int>> edges; for (int i = 0; i < vertexc - 1; i++) { int u, v, w; cin >> u >> v >> w; adj[u].push_back(make_pair(v, w)); adj[v].push_back(make_pair(u, w)); edges.push_back(make_pair(u, v)); } for (int i = 0; i < storec; i++) { int x; cin >> x; store[x] = true; } build_dp(root, root, 0); build_jmp(root, root); for (int i = 0; i < vertexc - 1; i++) { if (lvl[edges[i].first] < lvl[edges[i].second]) { swap(edges[i].first, edges[i].second); } } for (int i = 0; i < queryc; i++) { int forb_id, cur_vertex; cin >> forb_id >> cur_vertex; ll ans = query(cur_vertex, edges[forb_id - 1].first); if (ans == -1) { cout << "failed" << '\n'; } else if (ans == inf) { cout << "miss" << '\n'; } else { cout << ans << '\n'; } } }
Bình luận