diff --git a/_data/contests/37-PDP.yml b/_data/contests/37-PDP.yml index 2a066586..e63c636f 100644 --- a/_data/contests/37-PDP.yml +++ b/_data/contests/37-PDP.yml @@ -88,8 +88,8 @@ tiphunting: statement_pdf_url: "https://drive.google.com/file/d/1tFvY_3m4KCz4ldE3vyLLsidE-PYt1hQs/view" statement_md: true testcases_url: "" - solution: false - solution_author: "" - codes_in_git: false - solution_tags: [] + solution: true + solution_author: "Μάκης Αρσένης" + codes_in_git: true + solution_tags: [lca, dfs, trees, binary lifting] on_judge: false diff --git a/_includes/source_code/code/37-PDP/tiphunting/TASK b/_includes/source_code/code/37-PDP/tiphunting/TASK new file mode 100644 index 00000000..cc0ad524 --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/TASK @@ -0,0 +1,47 @@ +TASK( + name = "tiphunting", + test_count = 37, + files_dir = "testdata/37-PDP/tiphunting/", + input_file = "tiphunting.in", + output_file = "tiphunting.out", + time_limit = 2, + mem_limit = 128, + solutions = [ + SOLUTION( + name = "subtask1", + source = "subtask1.cc", + passes_only = [2, 3, 4, 5], + lang = "c++", + ), + SOLUTION( + name = "subtask2", + source = "subtask2.cc", + passes_only = [6, 7, 8, 9, 10], + lang = "c++", + ), + SOLUTION( + name = "subtask3", + source = "subtask3.cc", + passes_only = [1, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + lang = "c++", + ), + SOLUTION( + name = "subtask4", + source = "subtask4.cc", + passes_only = [6, 7, 8, 9, 10, 16, 17, 18, 19, 20, 21], + lang = "c++", + ), + SOLUTION( + name = "subtask5", + source = "subtask5.cc", + passes_only = [22, 23, 24, 25, 26, 27], + lang = "c++", + ), + SOLUTION( + name = "subtask6", + source = "optimal.cc", + passes_all, + lang = "c++", + ), + ] +) diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc new file mode 100644 index 00000000..0663e65b --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -0,0 +1,258 @@ +#include +#include +#include +#include + +using namespace std; + +using ll = pair; +using lll = tuple; +using vvll = vector>; +using vl = vector; +using vvl = vector>; + +vvll tree; +vl tip, depth, parent, parent_weight; +vvl pred; +vector best_subtree_tour, best_supertree_tour, best_supertree_root_walk; + +long long positive_part(long long x) { return max(0LL, x); } + +// Αρχικοποίηση όλων των global vectors για ένα δέντρο με `n` κορυφές. +void init(int n) { + // ceil(log2(max_N)) + constexpr int kMaxH = 18; + + tip.resize(n); + tree.resize(n); + + // Αρχικοποιώντας `depth[0] = 0`, `parent[0] = 0` θέτουμε την κορυφή + // 0 ως ρίζα του δέντρου. Η συνάρτηση `compute_auxiliary` συμπληρώνει + // τις τιμές και για τους υπόλοιπους κόμβους. + depth.resize(n, 0); + parent.resize(n, 0); + parent_weight.resize(n, 0); + + pred.resize(kMaxH, vector(n)); + best_subtree_tour.resize(n); + best_supertree_tour.resize(n); + best_supertree_root_walk.resize(n); +} + +// Διασχίζει το δέντρο `tree` ξεκινώντας από την κορυφή `u` και υπολογίζει +// αναδρομικά τις τιμές `depth[v]`, `parent[v]` και `parent_weight[v]` για κάθε +// κορυφή `v != u` στο υποδέντρο της `u`. Οι τιμές `depth[u]`, `parent[u]` και +// `parent_weight[u]` θα πρέπει να έχουν ήδη υπολογισθεί από τον caller. +// +// `depth[u]`: Το βάθος του `u` στο δέντρο, το οποίο ορίζεται ως το πλήθος των +// ακμών στο μονοπάτι από τον `u` προς τη ρίζα. Για παράδειγμα το βάθος της +// ρίζας είναι 0. `parent[u]`: Ο γονέας του `u`. `parent_weight[u]`: Κόστος +// του δρόμου που συνδέει τον `u` με τον γονέα του. +void compute_auxiliary(int u) { + for (auto [v, w]: tree[u]) { + if (v == parent[u]) continue; + parent[v] = u; + parent_weight[v] = w; + depth[v] = depth[u] + 1; + + compute_auxiliary(v); + } +} + +// Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές +// `best_subtree_tour` για την κορυφή `u` κι όλους τους απογόνους της. +// +// `best_subtree_tour[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει η +// κορυφή `u`. Mε άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει τον δρόμο +// `(u, parent)`. +void compute_best_subtree_tour(long u) { + best_subtree_tour[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent[u]) continue; + compute_best_subtree_tour(v); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); + } +} + +// Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές +// `subtree_root_opt` για την κορυφή `u` κι όλους τους απογόνους της, +// χρησιμοποιώντας τις τιμές `best_subtree_tour` που υπολογίσαμε ήδη στην +// προηγούμενη διάσχιση. +// +// `best_supertree_root_walk[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία +// ξεκινάει από την κορυφή `u`, καταλήγει στη ρίζα του δέντρου και μένει πάντα +// ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ +// προσμετράται. +void compute_best_supertree_root_walk(long u) { + best_supertree_root_walk[u] = 0; + + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent[u] != u) + best_supertree_root_walk[u] = + best_subtree_tour[parent[u]] + best_supertree_root_walk[parent[u]] + - positive_part(best_subtree_tour[u] - 2*parent_weight[u]) - parent_weight[u]; + + for (auto [v, w]: tree[u]) + if (v != parent[u]) + compute_best_supertree_root_walk(v); +} + +// Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές +// `best_subtree_tour` για την κορυφή `u` κι όλους τους απογόνους της, +// χρησιμοποιώντας τις τιμές `best_subtree_tour` που υπολογίσαμε ήδη στην +// προηγούμενη διάσχιση. +// +// best_supertree_tour[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά +// ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που +// ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. +void compute_best_supertree_tour(int u) { + best_supertree_tour[u] = 0; + + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent[u] != u) + best_supertree_tour[u] = + positive_part(best_subtree_tour[parent[u]] + best_supertree_tour[parent[u]] + - positive_part(best_subtree_tour[u] - 2*parent_weight[u]) - 2*parent_weight[u]); + + for (auto [v, w]: tree[u]) + if (v != parent[u]) + compute_best_supertree_tour(v); +} + +// Υπολογίζει τον πίνακα `pred` έτσι ώστε για κάθε 0 <= h <= H, 0 <= u < N: +// `pred[h][u] == v` αν και μόνο αν ο `v` είναι ο `2^h`-πρόγονος του `u`. +// Για παράδειγμα `pred[0][u] == parent[u]` γιατί ο γονέας του $u$ +// είναι ο `2^0 = 1`-ος πρόγονός του. +// O caller θα πρέπει να έχει ήδη υπολογίσει τον πίνακα parent +// (δες `compute_auxiliary`) έτσι ώστε η τιμή `parent[u]` να είναι +// ο γονέας της `u`, εκτός από την ρίζα `r` για την οποία `r == parent[r]`. +void compute_pred() { + const long n = parent.size(); + const long H = pred.size() - 1; + + for (long u = 0; u < n; ++u) + pred[0][u] = parent[u]; + + for (long h = 1; h <= H; ++h) + for (long u = 0; u < n; ++u) + pred[h][u] = pred[h - 1][pred[h - 1][u]]; +} + +// Υπολογίζει τρεις τιμές `(z, a, b)` όπου `z` είναι ο Ελάχιστος Κοινός Πρόγονος +// (LCA) των `u, v`, η `a` είναι η μοναδική κορυφή στο μονοπάτι από `a` προς `z` +// για την οποία `parent[u] == z` (ή `a == -1` αν τέτοια κορυφή δεν υπάρχει) και +// αντίστοιχα `b` είναι η μοναδική κορυφή στο μονοπάτι από `z` προς `v` τέτοια +// ώστε `parent[v] == z` (ή `b == -1` αν τέτοια κορυφή δεν υπάρχει). +// +// Η συνάρτηση χρησιμοποιεί τον πίνακα `pred` που υπολόγισε νωρίτερα η +// συνάρτηση `compute_pred` καθώς και τον πίνακα `depth` που υπολόγισε νωρίτερα +// η συνάρτηση `compute_auxiliary`. +lll lca(long u, long v) { + const long H = pred.size() - 1; + + if (u == v) + return {u, -1, -1}; + + if (depth[u] < depth[v]) { + auto [w, i, j] = lca(v, u); + return {w, j, i}; + } + + if (depth[u] != depth[v]) { + for (long h = H; h >= 0; h--) + if (depth[ pred[h][u] ] > depth[v]) + u = pred[h][u]; + + if (pred[0][u] == v) + return { v, u, -1 }; + + u = pred[0][u]; + } + + for (long h = H; h >= 0; --h) { + if (pred[h][u] != pred[h][v]) { + u = pred[h][u]; + v = pred[h][v]; + } + } + + return { pred[0][u], u, v }; +} + +int main() { + int subtask; + scanf("%i", &subtask); + + long n, q; + scanf("%li%li", &n, &q); + + init(n); + + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + compute_auxiliary(0); + + compute_pred(); + + compute_best_subtree_tour(0); + compute_best_supertree_tour(0); + compute_best_supertree_root_walk(0); + + for (long i = 0; i < q; ++i) { + long L, R; + scanf("%li%li", &L, &R); + L--, R--; + + if (L == R) { + printf("%lli\n", best_subtree_tour[L] + best_supertree_tour[L]); + continue; + } + + auto [z, u, v] = lca(L, R); + assert(u != -1 || v != -1); + + long long sol = 0; + if (u == -1) { + // Η κορυφή `L` είναι πρόγονος της `R`. + assert(z == L); + sol = best_supertree_root_walk[R] - best_supertree_root_walk[L] + + best_supertree_tour[L] + best_subtree_tour[R]; + } else if (v == -1) { + // Η κορυφή `R` είναι πρόγονος της `L`. + assert(z == R); + sol = best_supertree_root_walk[L] - best_supertree_root_walk[R] + + best_supertree_tour[R] + best_subtree_tour[L]; + } else { + // Οι κορυφές `L, R` έχουν κοινό πρόγονο τον `z != L, R`. + assert(pred[0][u] == z); + assert(pred[0][v] == z); + + sol = + best_supertree_root_walk[L] - best_supertree_root_walk[u] + best_subtree_tour[L] // (a) + + best_supertree_root_walk[R] - best_supertree_root_walk[v] + best_subtree_tour[R] // (b) + + best_subtree_tour[z] // (c1) + - positive_part(best_subtree_tour[u] - 2*parent_weight[u]) // (c2) + - positive_part(best_subtree_tour[v] - 2*parent_weight[v]) // (c3) + + best_supertree_tour[z] // (d) + - (parent_weight[u] + parent_weight[v]); // (e) + } + + printf("%lli\n", sol); + } + + return 0; +} diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc new file mode 100644 index 00000000..81f31ccc --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc @@ -0,0 +1,33 @@ +#include +#include + +using namespace std; + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 1); + + long n, q; + scanf("%li%li", &n, &q); + + long long sol = 0; + for (long i = 0; i < n; ++i) { + long t; + scanf("%li", &t); + sol += t; + } + + for (long i = 0; i < n-1; ++i) { + long tmp1, tmp2, tmp3; + scanf("%li%li%li", &tmp1, &tmp2, &tmp3); + } + + for (long i = 0; i < q; ++i) { + long L, R; + scanf("%li%li", &L, &R); + printf("%lli\n", sol); + } + + return 0; +} diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc new file mode 100644 index 00000000..882e776a --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc @@ -0,0 +1,66 @@ +#include +#include +#include + +using namespace std; + +using ll = pair; +using vl = vector; +using vvll = vector>; + +vvll tree; +vl tip; + +long long positive_part(long long x) { return max(0LL, x); } + +// Επιστρέφει το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// από την κορυφή `u`, καταλήγει πίσω σε αυτή και παραμένει +// εξ' ολοκλήρου μέσα στο υποδέντρο που ορίζει η `u`. Με άλλα λόγια, +// η διαδρομή απαγορεύεται να διασχίσει το δρόμο `(u, parent)`. +long long best_subtree_tour(long u, long parent) { + long long sol = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + long long s = best_subtree_tour(v, u); + sol += positive_part(s - 2*w); + } + + return sol; +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 2); + + long n, q; + scanf("%li%li", &n, &q); + + tip.resize(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + tree.resize(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + for (long i = 0; i < q; ++i) { + long L, R; + scanf("%li%li", &L, &R); + L--, R--; + assert(L == R); + + printf("%lli\n", best_subtree_tour(L, L)); + } + + return 0; +} diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc new file mode 100644 index 00000000..c7c3a47b --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc @@ -0,0 +1,93 @@ +#include +#include +#include + +using namespace std; + +using ll = pair; +using vl = vector; +using vvll = vector>; + +vvll tree; +vl tip; +vector best_subtree_tour, best_supertree_root_walk; + +long long positive_part(long long x) { return max(0LL, x); } + +// Πρώτη διάσχιση η οποία υπολογίζει το `best_subtree_tour` για την κορυφή `u` +// κι όλους τους απογόνους της. +// +// best_subtree_tour[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει και +// καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει η κορυφή +// `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει τον δρόμο `(u, +// parent)`. +void compute_best_subtree_tour(long u, long parent) { + best_subtree_tour[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + compute_best_subtree_tour(v, u); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); + } +} + +// Δεύτερη διάσχιση η οποία υπολογίζει το `best_supertree_root_walk` για την +// κορυφή `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `best_subtree_tour` που υπολογίσαμε ήδη στην πρώτη διάσχιση. +// +// best_supertree_root_walk[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και μένει πάντα ΕΚΤΟΣ του +// υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. +void compute_best_supertree_root_walk(long u, long parent, long w) { + best_supertree_root_walk[u] = 0; + + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent != u) + best_supertree_root_walk[u] = + best_subtree_tour[parent] + best_supertree_root_walk[parent] + - positive_part(best_subtree_tour[u] - 2*w) - w; + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_best_supertree_root_walk(v, u, w); +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 2 || subtask == 3); + + long n, q; + scanf("%li%li", &n, &q); + + tip.resize(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + tree.resize(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + best_subtree_tour.resize(n); + best_supertree_root_walk.resize(n); + + for (long i = 0; i < q; ++i) { + long L, R; + scanf("%li%li", &L, &R); + L--, R--; + compute_best_subtree_tour(R, R); + compute_best_supertree_root_walk(R, R, 0); + + printf("%lli\n", best_subtree_tour[L] + best_supertree_root_walk[L]); + } + + return 0; +} diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc new file mode 100644 index 00000000..9086cc5a --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc @@ -0,0 +1,95 @@ +#include +#include +#include + +using namespace std; + +using ll = pair; +using vl = vector; +using vvll = vector>; + +vvll tree; +vl tip; +vector best_subtree_tour, best_supertree_tour; + +long long positive_part(long long x) { return max(0LL, x); } + +// Πρώτη διάσχιση η οποία υπολογίζει το `best_subtree_tour` για την κορυφή `u` +// κι όλους τους απογόνους της. +// +// best_subtree_tour[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει και +// καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει η κορυφή +// `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει τον δρόμο `(u, +// parent)`. +void compute_best_subtree_tour(long u, long parent) { + best_subtree_tour[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + compute_best_subtree_tour(v, u); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); + } +} + +// Δεύτερη διάσχιση η οποία υπολογίζει το `best_supertree_tour` για την κορυφή +// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `best_subtree_tour` που υπολογίσαμε ήδη στην πρώτη διάσχιση. +// +// best_supertree_tour[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά +// ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που +// ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. +void compute_best_supertree_tour(long u, long parent, long w) { + best_supertree_tour[u] = 0; + + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent != u) + best_supertree_tour[u] = + positive_part(best_subtree_tour[parent] + best_supertree_tour[parent] + - positive_part(best_subtree_tour[u] - 2*w) - 2*w); + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_best_supertree_tour(v, u, w); +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 2 || subtask == 4); + + long n, q; + scanf("%li%li", &n, &q); + + tip.resize(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + tree.resize(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + best_subtree_tour.resize(n); + compute_best_subtree_tour(0, 0); + + best_supertree_tour.resize(n); + compute_best_supertree_tour(0, 0, 0); + + for (long i = 0; i < q; ++i) { + long L, R; + scanf("%li%li", &L, &R); + L--, R--; + assert(L == R); + + printf("%lli\n", best_subtree_tour[L] + best_supertree_tour[L]); + } + + return 0; +} diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc new file mode 100644 index 00000000..8152850f --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc @@ -0,0 +1,103 @@ +#include +#include +#include + +using namespace std; + +using ll = pair; +using vl = vector; +using vvll = vector>; + +vvll tree; +vl tip; +vector best_subtree_tour, best_supertree_root_walk; + +long long positive_part(long long x) { return max(0LL, x); } + +// Πρώτη διάσχιση η οποία υπολογίζει το `best_subtree_tour` για την κορυφή `u` +// κι όλους τους απογόνους της. +// +// best_subtree_tour[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει και +// καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει η κορυφή +// `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει τον δρόμο `(u, +// parent)`. +void compute_best_subtree_tour(long u, long parent) { + best_subtree_tour[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + compute_best_subtree_tour(v, u); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); + } +} + +// Δεύτερη διάσχιση η οποία υπολογίζει το `best_supertree_root_walk` για την +// κορυφή `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `best_subtree_tour` που υπολογίσαμε ήδη στην πρώτη διάσχιση. +// +// best_supertree_root_walk[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και μένει πάντα ΕΚΤΟΣ του +// υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. +void compute_best_supertree_root_walk(long u, long parent, long w) { + best_supertree_root_walk[u] = 0; + + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent != u) + best_supertree_root_walk[u] = + best_subtree_tour[parent] + best_supertree_root_walk[parent] + - positive_part(best_subtree_tour[u] - 2*w) - w; + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_best_supertree_root_walk(v, u, w); +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 5); + + long n, q; + scanf("%li%li", &n, &q); + + tip.resize(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + tree.resize(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + long L, R; + scanf("%li%li", &L, &R); + L--, R--; + + best_subtree_tour.resize(n); + compute_best_subtree_tour(L, L);; + + best_supertree_root_walk.resize(n); + compute_best_supertree_root_walk(L, L, 0); + + // Απάντηση στο πρώτο ερώτημα. + printf("%lli\n", best_subtree_tour[R] + best_supertree_root_walk[R]); + + // Απάντηση στα υπόλοιπα `q-1` ερωτήματα. + for (long i = 1; i < q; ++i) { + long new_L; + scanf("%li%li", &new_L, &R); + assert(L == new_L - 1); + R--; + + printf("%lli\n", best_subtree_tour[R] + best_supertree_root_walk[R]); + } + + return 0; +} diff --git a/contests/_37-PDP/b-tiphunting-solution.md b/contests/_37-PDP/b-tiphunting-solution.md new file mode 100644 index 00000000..4ffd46f0 --- /dev/null +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -0,0 +1,365 @@ +--- +layout: solution +codename: tiphunting +--- + +## Επεξήγηση Εκφώνησης + +Μας δίνεται η περιγραφή ενός οδικού δικτύου μιας πόλης με $$N$$ σπίτια που +συνδέονται μεταξύ τους με $$N-1$$ δρόμους διπλής κατεύθυνσης. Η εκφώνηση μας +διαβεβαιώνει ότι η πόλη έχει σχεδιαστεί ώστε να υπάρχει (μοναδικό) μονοπάτι που +να συνδέει μεταξύ τους οποιαδήποτε δύο σπίτια. Συμπεραίνουμε λοιπόν ότι το +γράφημα που αναπαριστά την πόλη είναι ένα δέντρο, όπου οι κορυφές αντιστοιχούν +σε σπίτια και οι ακμές σε δρόμους. + +Κάθε σπίτι $$i$$ μπορεί να προσφέρει ένα μη-αρνητικό φιλοδώρημα $$t_i$$, και +κάθε δρόμος $$j$$ που συνδέει δύο σπίτια $$u, v$$ έχει ένα μη-αρνητικό κόστος +διάσχισης που θα συμβολίζουμε με $$w_j$$ ή ισοδύναμα με $$w_{u, v}$$. Ως +_διαδρομή_ ορίζουμε μια πεπερασμένη ακολουθία σπιτιών όπου διαδοχικά σπίτια +συνδέονται μεταξύ τους άμεσα από κάποιον δρόμο. Παρατηρήστε ότι ο παραπάνω +ορισμός επιτρέπει σε μια διαδρομή να περιλαμβάνει το ίδιο σπίτι _περισσότερες +από μια φορές_. + +Το _κέρδος_ μιας διαδρομής ορίζεται ως το άθροισμα των φιλοδωρημάτων των +σπιτιών από τις οποίες αποτελείται (κάθε σπίτι μπορεί να συνεισφέρει το +φιλοδώρημα του **το πολύ μια φορά**, ακόμα κι αν η διαδρομή το επισκέπτεται +περισσότερες φορές) μείον το συνολικό κόστος διάσχισης των δρόμων της +διαδρομής, όπου, σε αντίθεση με το άθροισμα των φιλοδωρημάτων, το κόστος κάθε +δρόμου προσμετράται **κάθε φορά** που η διαδρομή τον διασχίζει. + +Το αρχείο εισόδου περιέχει $$Q$$ ερωτήματα. Κάθε ερώτημα αποτελείται από +δύο σπίτια $$L, R$$ και μας ζητάει να υπολογίσουμε το μέγιστο δυνατό κέρδος +μιας διαδρομής με αφετηρία το σπίτι $$L$$ και προορισμό το σπίτι $$R$$. + +## Υποπρόβλημα 1 ($$w_1 = w_2 = \ldots = w_{N-1} = 0$$) + +Σε αυτό το υποπρόβλημα το κόστος διάσχισης κάθε δρόμου είναι μηδενικό, +επομένως η διαδρομή μας μπορεί να επισκεφτεί όλα τα σπίτια και να μαζέψει +όλα τα φιλοδωρήματα, ανεξαρτήτως αφετηρίας και προορισμού. +Η απάντηση λοιπόν σε κάθε ερώτημα είναι η ίδια, και ίση με το συνολικό +άθροισμα $$S = \sum_{i = 1}^N t_i$$ των φιλοδωρημάτων. + +Δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md +solution_name='subtask1.cc' %}) + +## Γενικές Παρατηρήσεις + +Πριν προχωρήσουμε, ας παρατηρήσουμε κάποιες ιδιότητες της βέλτιστης λύσης που +θα μας φανούν χρήσιμες σε όλα τα υποπροβλήματα που ακολουθούν. + +**Παρατήρηση 1**: +Στη βέλτιστη λύση δεν θα χρειαστεί ποτέ να διασχίσουμε κάποιο δρόμο πάνω από +δύο φορές. + +**Παρατήρηση 2**: +Η βέλτιστη λύση θα είναι μια διαδρομή η οποία θα περιλαμβάνει το (μοναδικό) +μονοπάτι που συνδέει τα σπίτια $$L$$ και $$R$$, όπου σε κάθε στάση σε κάποιο +σπίτι $$u$$, θα έχουμε την ευκαιρία να "ξεφύγουμε" για λίγο από το βασικό του +μονοπάτι και να εξερευνήσουμε τα σπίτια που συνδέονται με το $$u$$ (άμεσα ή +έμμεσα μέσω άλλων δρόμων), να μαζέψουμε όσα περισσότερα φιλοδωρήματα μπορούμε, +να επιστρέψουμε πάλι στο $$u$$ και να συνεχίσουμε προς τον τελικό μας +προορισμό. Έτσι, στο τέλος θα έχουμε διασχίσει τους δρόμους που βρίσκονται στο +κεντρικό μονοπάτι μεταξύ $$L$$ και $$R$$ μόνο μια φορά, και για κάθε άλλο δρόμο +θα τον έχουμε διασχίσει είτε καμία είτε δύο φορές. + +**Παρατήρηση 3**: +Η απάντηση στο ερώτημα $$L, R$$ είναι ίδια με την απάντηση στο ερώτημα $$R, +L$$ λόγω συμμετρίας (οι δρόμοι είναι διπλής κατεύθυνσης). + +## Υποπρόβλημα 2 ($$N \le 1.000, Q \le 1.000, L = R$$) --- Λύση $$\mathcal{O}(N \cdot Q)$$ + +Ο περιορισμός $$L = R$$ απλουστεύει το πρόβλημα καθώς δεν χρειάζεται να +ασχοληθούμε με το μέρος της λύσης που περιλαμβάνει το μονοπάτι που αναφέραμε +στην Παρατήρηση 2, παρά μόνο με την επιμέρους εξερεύνηση. + +Ας φανταστούμε ότι το σπίτι $$L$$ από το οποίο ξεκινάμε είναι η ρίζα του +δέντρου που αναπαριστά το οδικό δίκτυο. Κάθε σπίτι $$u$$ που συνδέεται άμεσα με +το $$L$$ ορίζει ένα _υποδέντρο_ το οποίο θα συμβολίζουμε με +$$\text{subtree}(u)$$. Για κάθε ένα από αυτά τα δέντρα, έχουμε να κάνουμε μια +ανεξάρτητη επιλογή: +1. _να διασχίσουμε_ το δρόμο $$L \rightarrow u$$ κόστους $$w$$, να μαζέψουμε + όσα περισσότερα φιλοδωρήματα μπορούμε από το $$\text{subtree}(u)$$ και να + επιστρέψουμε πίσω διασχίζοντας το δρόμο $$u \rightarrow L$$ για δεύτερη + φορά, ή +2. να _μη διασχίσουμε ποτέ_ το δρόμο που συνδέει το σπίτι $$L$$ με το σπίτι + $$u$$. + +Ας ορίσουμε $$\text{best\_subtree\_tour}[u]$$ ως το μέγιστό δυνατό κέρδος +που μπορούμε να έχουμε αν ξεκινήσουμε από το σπίτι $$u$$, κινούμαστε μόνο μέσα +στο υποδέντρο που ορίζει το σπίτι $$u$$, και τελικά επιστρέψουμε πάλι στο +$$u$$. + +Είναι φανερό ότι θα ακολουθήσουμε την επιλογή 1 όταν αυτή παράγει κέρδος, +δηλαδή όταν $$\text{best\_subtree\_tour}[u] - 2\cdot w \ge 0$$. +Διαφορετικά η επιλογή 2 είναι καλύτερη --- ή ισοδύναμη σε περίπτωση μηδενικού +κέρδους. + +Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{best\_subtree\_tour}$$ +εφαρμόζοντας τον παρακάτω αναδρομικό τύπο: + +$$ \text{best\_subtree\_tour}[u] = t_u + \sum_{v \in \text{children}(u)} +\left( \text{best\_subtree\_tour}[v] - 2 \cdot w_{u, v} \right)^+ $$ + +όπου ο συμβολισμός $$(x)^+$$ σημαίνει $$\max(0, x)$$ και $$\text{children}(u)$$ +είναι το σύνολο με όλα τα παιδιά της κορυφής $$u$$. + +{% include code.md solution_name='subtask2.cc' start=16 end=30 %} + +Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{best\_subtree\_tour}[L]$$. + +Για τον υπολογισμό όλων των παραπάνω τιμών, χρειάζεται για κάθε κορυφή να +υπολογίσουμε ένα άθροισμα που περιλαμβάνει όλα τα παιδιά της, συνεπώς +χρειάζεται χρόνος γραμμικός στο πλήθος των κορυφών και των ακμών του δέντρου, +άρα $$\mathcal{O}(N)$$ για κάθε ερώτημα. Συνολικά η λύση αυτή έχει +πολυπλοκότητα $$\mathcal{O}(N \cdot Q)$$, η οποία μας καλύπτει για τους +περιορισμούς αυτού του υποπροβλήματος. + +Ο πλήρης κώδικας βρίσκεται [εδώ]({% include link_to_source.md +solution_name='subtask2.cc' %}) + +## Υποπρόβλημα 3 ($$N \le 1.000, Q \le 1.000$$) --- Λύση $$\mathcal{O}(N Q)$$ + +Ας σκεφτούμε τώρα την πιο γενική περίπτωση όπου η διαδρομή μας θα πρέπει να +καταλήγει σε κάποιο σπίτι $$R$$, πιθανώς διαφορετικό του $$L$$. Μπορούμε να +ξεκινήσουμε παρόμοια με πριν, θεωρώντας αυτή τη φορά ότι η κορυφή $$R$$ είναι η +ρίζα του δέντρου. Η αφετηρία $$L$$ θα βρίσκεται σε κάποιο μικρότερο υποδέντρο. +Ξεκινώντας από την $$L$$, είμαστε ελεύθεροι να εξερευνήσουμε το υποδέντρο +$$\text{subtree}(L)$$ για το οποίο μπορούμε να υπολογίσουμε τη βέλτιστη +διαδρομή χρησιμοποιώντας το $$\text{best\_subtree\_tour}[L]$$ που ορίσαμε +προηγουμένως. + +Σε αντίθεση όμως με πριν, μόλις επιστρέψουμε στο $$L$$ θα πρέπει να ανέβουμε +ένα επίπεδο παραπάνω, ακολουθώντας το μονοπάτι προς τη ρίζα και μαζεύοντας κι +άλλα φιλοδωρήματα στην πορεία. Ας ορίσουμε λοιπόν την ποσότητα +$$\text{best\_supertree\_root\_walk}[u]$$ ως το μέγιστο κέρδος που +μπορούμε να έχουμε όταν ξεκινάμε από το σπίτι $$u$$ (χωρίς όμως να συλλέγουμε +το φιλοδώρημα του $$u$$), καταλήγουμε τελικά στη ρίζα $$R$$ του δέντρου, και +σε όλη τη διαδρομή απαγορεύεται να επισκεφτούμε οποιοδήποτε σπίτι βρίσκεται στο +υποδέντρο $$\text{subtree}(u)$$. + +Εάν υπολογίσουμε την τιμή της παραπάνω ποσότητας για την κορυφή $$L$$, τότε +μπορούμε να υπολογίσουμε την απάντηση του ερωτήματος απλά με το άθροισμα: + +$$ \text{best\_subtree\_tour}[L] + \text{best\_supertree\_root\_walk}[L]. $$ + +Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{best\_supertree\_root\_walk}[u]$$ +από "πάνω προς τα κάτω" χρησιμοποιώντας τις τιμές $$\text{best\_subtree\_tour}$$ +ως εξής: αν η κορυφή $$u$$ είναι η ρίζα, τότε εξ +ορισμού $$\text{best\_supertree\_root\_walk}[u] = 0$$, διαφορετικά, + +$$ +\begin{align*} + \text{best\_supertree\_root\_walk}[u] =& + \text{best\_supertree\_root\_walk}[\text{parent}(u)] + + \text{best\_subtree\_tour}[\text{parent}(u)] \\ + &- (\text{best\_subtree\_tour}[u] - 2 \cdot w_{u, \text{parent}(u)})^+ + - w_{u, \text{parent}(u)} +\end{align*} +$$ + +Όντως, ο παραπάνω τύπος χρησιμοποιεί την τιμή του +$$\text{best\_supertree\_root\_walk}$$ για τον γονέα $$\text{parent(u)}$$ του $$u$$, +που μπορούμε να θεωρήσουμε ότι έχουμε ήδη υπολογίσει (θυμηθείτε ότι +υπολογίζουμε τις τιμές από πάνω προς τα κάτω), η οποία περιλαμβάνει τη βέλτιστη +λύση που αφορά τη διαδρομή από $$\text{parent}(u)$$ μέχρι τη ρίζα. Το μόνο που +μένει είναι να συμπεριλάβουμε το κομμάτι της διαδρομής που εξερευνά τα σπίτια +που συνδέονται στο $$u$$ και στον γονέα του. Η τιμή για αυτό το κομμάτι της +διαδρομής είναι _σχεδόν_ η τιμή $$\text{best\_subtree\_tour}[\text{parent}(u)]$$ +που είχαμε ορίσει προηγουμένως, με τη διαφορά ότι αυτή η τιμή πιθανώς να +περιλαμβάνει το κόστος της ακμής $$(u, \text{parent}[u])$$ _δύο φορές_. Πρέπει +λοιπόν να διορθώσουμε την τιμή, αφαιρώντας την κατάλληλη ποσότητα, μόνο όμως +όταν αυτή έχει θετικό πρόσημο. Τέλος, σε κάθε περίπτωση, συμπεριλαμβάνουμε και +το κόστος διάσχισης του δρόμου $$(u, \text{parent}[u])$$ ακριβώς μία φορά. + +Μια αναδρομική υλοποίηση είναι η παρακάτω: + +{% include code.md solution_name='subtask3.cc' start=17 end=53 %} + +Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διασχίσεις_ του δέντρου +(μια για τον υπολογισμό των $$\text{best\_subtree\_tour}$$, και έπειτα μια για +τον υπολογισμό των $$\text{best\_supertree\_root\_walk}$$), οι οποίες γίνονται σε +$$\mathcal{O}(N)$$ χρόνο. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα +και γι' αυτό το υποπρόβλημα. +Μπορείτε να δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md +solution_name='subtask3.cc' %}). + +## Υποπρόβλημα 4 ($$L = R$$) --- Λύση $$\mathcal{O}(N + Q)$$ + +Σε αυτό το υποπρόβλημα το μέγεθος του δέντρου και το πλήθος των ερωτημάτων +είναι αρκετά μεγαλύτερο από το προηγούμενο και έτσι η λύση που περιγράψαμε +νωρίτερα για το υποπρόβλημα 2 δεν επαρκεί. Θα πρέπει να βρούμε κάποιον τρόπο να +απαντάμε τα ερωτήματα χωρίς να διαπερνάμε όλο το δέντρο από την αρχή κάθε φορά. +Ο λόγος που χρειαζόταν να διασχίσουμε το δέντρο από την αρχή σε κάθε ερώτημα, +ήταν ότι έπρεπε να αλλάξουμε ρίζα, κι έτσι οι τιμές που είχαμε για το +$$\text{best\_subtree\_tour}$$ δεν θα ήταν έγκυρες για το δέντρο του +επόμενου ερωτήματος. Παρ' όλα αυτά, ας προσπαθήσουμε να σκεφτούμε αν μπορούμε +με κάποιο τρόπο να τις εκμεταλλευτούμε ώστε να απαντήσουμε μελλοντικά ερωτήματα. + +Ας θεωρήσουμε λοιπόν στο εξής ότι η ρίζα του δέντρου είναι πάντα η κορυφή 1, κι +ας υπολογίσουμε τις τιμές $$\text{best\_subtree\_tour}$$ όπως τις ορίσαμε +παραπάνω. Η απάντηση σε ένα ερώτημα στο οποίο $$L = R \ne 1$$, είναι _σχεδόν_ +ίση με $$\text{best\_subtree\_tour}[L]$$, με τη διαφορά ότι αυτή η τιμή δεν +περιλαμβάνει διαδρομές που ανεβαίνουν σε υψηλότερα επίπεδα του δέντρου και +γυρνάνε πάλι πίσω στην κορυφή $$L$$. Αυτό όμως διορθώνεται εύκολα αν +προ-υπολογίσουμε για κάθε κορυφή $$u$$ αυτή την τιμή, ας την ονομάσουμε +$$\text{best\_\textcolor{red}{supertree}\_tour}[u]$$, ώστε να την έχουμε +διαθέσιμη όταν τη χρειαστούμε για να απαντήσουμε κάποιο ερώτημα. + +Μάλιστα, οι τιμές αυτές μοιάζουν πολύ με τις τιμές $$\text{best\_supertree\_root\_walk}$$ που +ορίσαμε στο προηγούμενο υποπρόβλημα, με τη διαφορά όμως ότι δεν είναι πλέον αναγκαίο +να φτάσουμε μέχρι τη ρίζα, αλλά πρέπει να γυρίσουμε πίσω στην κορυφή $$u$$, +το οποίο σημαίνει ότι πρέπει να μετρήσουμε το κόστος του δρόμου +$$(u, \text{parent}(u))$$ **δύο φορές**. Ο τύπος που είχαμε προηγούμενως +αλλάζει ως εξής: + +$$ +\begin{align*} +\text{best\_supertree\_tour}[u] = + ( & \text{best\_supertree\_tour}[\text{parent}(u)] + + \text{best\_subtree\_tour}[\text{parent}(u)] \\ + &- (\text{best\_subtree\_tour}[u] - 2 \cdot w_{u, \text{parent}(u)})^+ + - \textcolor{red}{2} \cdot w_{u, \text{parent}(u)} )^+ +\end{align*} +$$ + +Ο προ-υπολογισμός των $$\text{best\_subtree\_tour}$$ και +$$\text{best\_supertree\_tour}$$ με ρίζα την κορυφή 1 μπορεί να γίνει με δύο +διασχίσεις του δέντρου σε χρόνο $$\mathcal{O}(N)$$ πριν ξεκινήσουμε να απαντάμε +ερωτήματα, κι έπειτα για κάθε ερώτημα, μπορούμε να υπολογίσουμε την απάντηση σε +σταθερό χρόνο με τον τύπο: + +$$ \text{best\_subtree\_tour}[L] + \text{best\_supertree\_tour}[L]. $$ + +Συνολικά η λύση αυτή έχει χρονική πολυπλοκότητα $$\mathcal{O}(N + Q)$$. +Μπορείτε να δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md +solution_name='subtask4.cc' %}). + +## Υποπρόβλημα 5 ($$L_1 = L_2 = \ldots = L_N$$) --- Λύση $$\mathcal{O}(N + Q)$$ + +Σε αυτή την περίπτωση η αφετηρία και ο προορισμός μπορεί να είναι διαφορετικά +σπίτια, όμως η εκφώνηση μας εξασφαλίζει ότι οι αφετηρίες όλων των ερωτημάτων +θα είναι οι ίδιες. Αυτό μας επιτρέπει να θέσουμε την κοινή κορυφή $$L$$ ως ρίζα +και να εφαρμόσουμε τη λύση του υποπροβλήματος 3, υπολογίζοντας όμως +τις βοηθητικές τιμές $$\text{best\_subtree\_tour}$$ και $$\text{best\_supertree\_root\_walk}$$ +μόνο μια φορά στην αρχή, και απαντώντας μετά το κάθε ερώτημα σε σταθερό χρόνο. +Ο πλήρης κώδικας βρίσκεται [εδώ]({% include link_to_source.md +solution_name='subtask5.cc' %}). + +**Σημείωση**: Στο υποπρόβλημα 3 θεωρήσαμε ότι η ρίζα ήταν το σπίτι $$R$$, όχι +το $$L$$ όπως εδώ. Η Παρατήρηση 3 όμως μας εξασφαλίζει ότι αυτή η αλλαγή +δεν επηρεάζει την απάντηση. + +## Υποπρόβλημα 6 + +Ας προσπαθήσουμε να γενικεύσουμε την προηγούμενη λύση στην περίπτωση που +η ρίζα του δέντρου με βάση την οποία προ-υπολογίσαμε τις βοηθητικές +τιμές δεν είναι ούτε η αφετηρία ούτε ο προορισμός. Θα ξεκινήσουμε περιγράφοντας +τη μορφή της βέλτιστης διαδρομής, κι έπειτα θα δούμε πώς να υπολογίσουμε +το κέρδος της σε υπο-γραμμικό χρόνο. + +Το μονοπάτι που συνδέει τις κορυφές $$L$$ και $$R$$ θα έχει την εξής μορφή: +ξεκινώντας από την $$L$$ ανεβαίνουμε (0 ή περισσότερα) επίπεδα μέχρι να φτάσουμε +στον Ελάχιστο Κοινό Πρόγονο (Lowest Common Ancestor) των $$L$$ και $$R$$, +τον οποίο θα συμβολίζουμε με $$\text{LCA}(L, R)$$, κι έπειτα κινούμαστε προς χαμηλότερα +επίπεδα μέχρι να φτάσουμε στην κορυφή $$R$$. Από κάθε κόμβο αυτού του μονοπατιού, +μπορούμε να εξερευνήσουμε υποδέντρα ώστε να μαζέψουμε φιλοδωρήματα (ακριβώς όπως +στον ορισμό του $$\text{best\_subtree\_tour}$$). Επιπλέον, από +την κορυφή $$\text{LCA}(L, R)$$ έχουμε τη δυνατότητα να ακολουθήσουμε μια +κυκλική διαδρομή που επισκέπτεται υψηλότερα επίπεδα του δέντρου (ακριβώς +όπως στον ορισμό του $$\text{best\_supertree\_tour}$$ παραπάνω). + +Για την ώρα, ας υποθέσουμε ότι γνωρίζουμε τον ελάχιστο κοινό πρόγονο $$z = +\text{LCA}(L, R)$$. + +Έχουμε τις παρακάτω περιπτώσεις: + +- $$z = L$$: Η περίπτωση αυτή μοιάζει πολύ με το υποπρόβλημα 5, με τη διαφορά + ότι αφετηρία δεν είναι η ρίζα του δέντρου και συνεπώς αν εφαρμόσουμε τον τύπο + $$\text{best\_subtree\_tour}[R] + \text{best\_supertree\_root\_walk}[R]$$ θα πάρουμε + λάθος απάντηση καθώς ο δεύτερος όρος περιέχει το κέρδος της λύσης που + ανεβαίνει μέχρι τη ρίζα αντί αυτής που καταλήγει στην κορυφή $$z$$. Αυτό όμως + διορθώνεται εύκολα αν **αφαιρέσουμε** το κέρδος που αντιστοιχεί στο κομμάτι + της διαδρομής που ξεκινάει από την κορυφή $$z$$ και φτάνει στη ρίζα (το + έχουμε ήδη υπολογίσει, είναι το $$\text{best\_supertree\_root\_walk}(z)$$) και αντ' + αυτού **προσθέσουμε** το κέρδος της _κυκλικής_ διαδρομής που ξεκινάει και + επιστρέφει στο $$z$$, μαζεύοντας φιλοδωρήματα από κόμβους εκτός του + $$\text{subtree}(z)$$ (κι αυτή η τιμή είναι διαθέσιμη στο + $$\text{best\_supertree\_tour}(z)$$). Συνοψίζοντας, η + απάντηση σε αυτή την περίπτωση είναι: + + $$ + \begin{align*} + &\text{best\_supertree\_root\_walk}(L) + \text{best\_subtree\_tour}(R) \\ + &-\text{best\_supertree\_root\_walk}(z) + \text{best\_supertree\_tour}(z) + \end{align*} + $$ + +- $$z = L$$: Αντίστοιχα με την προηγούμενη περίπτωση, η απάντηση είναι: + + $$ + \begin{align*} + &\text{best\_supertree\_root\_walk}(R) + \text{best\_subtree\_tour}(L) \\ + &-\text{best\_supertree\_root\_walk}(z) + \text{best\_supertree\_tour}(z) + \end{align*} + $$ + +- $$z \neq L, R$$: Αυτή η περίπτωση είναι συνδυασμός των δύο παραπάνω. Πράγματι, + αν μας ρώταγαν ξεχωριστά τα ερωτήματα $$L, z$$ κι έπειτα $$z, R$$, το άθροισμα των + απαντήσεων είναι κοντά στην λύση που παρόντος ερωτήματος. Χρειάζεται + όμως προσοχή ώστε να μην διπλομετρήσουμε ορισμένες υπο-διαδρομές, + για παράδειγμα εκείνες τις κυκλικές διαδρομές που ξεκινάνε και τερματίζουν + στην κορυφή $$z$$. + + Για να διορθώσουμε την απάντηση, θα φανεί χρήσιμο να γνωρίσουμε τις κορυφές + $$u, v$$ οι οποίες είναι τα παιδιά της $$z$$ που οδηγούν προς τις κορυφές + $$L$$ και $$R$$ αντίστοιχα. Ας υποθέσουμε ότι τις γνωρίζουμε -- + μπορούμε να τις βρούμε κατα τον υπολογισμό της $$z$$ όπως αναφέρουμε + παρακάτω. Έχοντας τις $$u, v$$, υπολογίζουμε την απάντηση του + ερωτήματος συνδυάζοντας τις παρακάτω τιμές: + - (a) το κέρδος της βέλτιστης διαδρομή από την κορυφή $$L$$ μέχρι την + κορυφή $$u$$, έπειτα + - (b) από την $$R$$ μέχρι την $$v$$, + - (c) το κόστος όλων των κυκλικών διαδρομών που ξεκινάνε και καταλήγουν + στην $$z$$ παραμένοντας στο υποδέντρο της $$z$$ χωρίς όμως να + επισκέπτονται τα υποδέντρα των $$u, v$$ (τα έχουμε ήδη συμπεριλάβει), + - (d) το κόστος της κυκλικής + διαδρομής από το $$z$$ στον εαυτό του "προς τα πάνω" (αποφεύγοντας δηλαδή + το $$\text{subtree}(z)$$), και τέλος + - (e) το κόστος των δρόμων + $$(u, z)$$ και $$(v, z)$$. + + Συμπερασματικά, ο τύπος για τον υπολογισμό της απάντησης του ερωτήματος + $$L, R$$ για την περίπτωση $$LCA(L, R) \neq L, R$$ είναι: + + $$ + \begin{align*} + &\underbrace{(\text{best\_supertree\_root\_walk}[L] + - \text{best\_supertree\_root\_walk}[u] - \text{best\_subtree\_tour}[L])}_{(a)} \\ + +&\underbrace{(\text{best\_supertree\_root\_walk}[R] + - \text{best\_supertree\_root\_walk}[v] - \text{best\_subtree\_tour}[R])}_{(b)} \\ + +&\underbrace{( + \text{best\_subtree\_tour}[z] + - (\text{best\_subtree\_tour}[u] - 2\cdot w_{u, z})^+ + - (\text{best\_subtree\_tour}[v] - 2\cdot w_{v, z})^+) + }_{(c)} \\ + +&\underbrace{\text{best\_supertree\_tour}[z]}_{(d)} \\ + -&\underbrace{(w_{u, z} + w_{v, z})}_{(e)} + \end{align*} + $$ + +Είδαμε λοιπόν πώς να απαντάμε τα ερωτήματα γρήγορα (και μάλιστα σε σταθερό +χρόνο), αρκεί να μπορούμε να υπολογίσουμε τον ελάχιστο κοινό πρόγονο μαζί με +τις κορυφές $$u, v$$ όπως τις ορίσαμε παραπάνω. + +Για να το κάνουμε αυτό αποδοτικά, μπορούμε να χρησιμοποιήσουμε μια τεχνική που +λέγεται **binary lifting** και η οποία μας επιτρέπει να υπολογίσουμε +τον ελάχιστο κοινό πρόγονο δύο κορυφών σε χρόνο $$\mathcal{O}(\log N)$$, έχοντας +κάνει κατάλληλη προεργασία σε χρόνο $$\mathcal{O}(N \log N)$$ μια φορά στην +αρχή. Προτείνουμε να ανατρέξετε +[εδώ](https://cp-algorithms.com/graph/lca_binary_lifting.html) για μια +αναλυτική περιγραφή, ή δείτε τα σχόλια στον κώδικα της λύσης +[εδώ]({% include link_to_source.md solution_name='optimal.cc' %}). + +Η λύση που περιγράψαμε έχει συνολική πολυπλοκότητα $$\mathcal{O} +( (N + Q) \log N )$$ η οποία μας καλύπτει για τους περιορισμούς +του προβλήματος.