From 6be28dc463bcc8e5bb54ecf1ae3df486a16a9faf Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Wed, 19 Mar 2025 21:47:28 -0700 Subject: [PATCH 01/16] Second draft of tiphunting with codes --- _data/contests/37-PDP.yml | 6 +- .../code/37-PDP/tiphunting/optimal.cc | 263 ++++++++++++++ .../code/37-PDP/tiphunting/subtask1.cc | 33 ++ .../code/37-PDP/tiphunting/subtask2.cc | 65 ++++ .../code/37-PDP/tiphunting/subtask3.cc | 91 +++++ .../code/37-PDP/tiphunting/subtask4.cc | 90 +++++ .../code/37-PDP/tiphunting/subtask5.cc | 98 +++++ contests/_37-PDP/b-tiphunting-solution.md | 336 ++++++++++++++++++ 8 files changed, 979 insertions(+), 3 deletions(-) create mode 100644 _includes/source_code/code/37-PDP/tiphunting/optimal.cc create mode 100644 _includes/source_code/code/37-PDP/tiphunting/subtask1.cc create mode 100644 _includes/source_code/code/37-PDP/tiphunting/subtask2.cc create mode 100644 _includes/source_code/code/37-PDP/tiphunting/subtask3.cc create mode 100644 _includes/source_code/code/37-PDP/tiphunting/subtask4.cc create mode 100644 _includes/source_code/code/37-PDP/tiphunting/subtask5.cc create mode 100644 contests/_37-PDP/b-tiphunting-solution.md diff --git a/_data/contests/37-PDP.yml b/_data/contests/37-PDP.yml index 2a066586..42953753 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: true + solution_author: "Μάκης Αρσένης" + codes_in_git: true solution_tags: [] on_judge: false 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..247a16aa --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -0,0 +1,263 @@ +#include +#include +#include +#include +// #define DEBUG + +#ifdef DEBUG +#define debug(...) fprintf(stderr, __VA_ARGS__) +#else +#define debug(...) +#endif + +using namespace std; + +using ii = pair; +using iii = pair>; +using vvii = vector>; +using vvi = vector>; + +long positive_part(long x) { return max(0L, x); } + +// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// κι όλους τους απογόνους της. +// +// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// τον δρόμο `(u, parent)`. +void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { + subtree_loop_opt[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + } +} + +// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή +// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// +// supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και +// μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής +// `u` ΔΕΝ προσμετράται. +void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { + assert(0 <= u && u < tree.size()); + assert(-1 <= parent && parent < (long)tree.size()); + + supertree_root_opt[u] = 0; + + if (parent != -1) + supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, v, u, w); +} + +// Τρίτη διαπέραση η οποία υπολογίζει το `supertree_loop_opt` για την κορυφή +// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// +// supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά +// ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που +// ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. +void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { + assert(0 <= u && u < tree.size()); + assert(-1 <= parent && parent < (long)tree.size()); + + supertree_loop_opt[u] = 0; + + if (parent != -1) + supertree_loop_opt[u] = positive_part(subtree_loop_opt[parent] + supertree_loop_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - 2*w); + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, v, u, w); +} + +void preprocess(const vvii& tree, int u, vector& depth, vector& parent, vector& parent_weight) { + const int n = tree.size(); + assert(depth.size() >= n); + assert(parent.size() >= n); + assert(parent_weight.size() >= n); + assert(0 <= u && u < n); + + for (auto [v, w]: tree[u]) { + if (v == parent[u]) continue; + assert(0 <= v && v < n); + + parent[v] = u; + parent_weight[v] = w; + depth[v] = depth[u] + 1; + + preprocess(tree, v, depth, parent, parent_weight); + } +} + +// Set `pred[h][u] = v` when `v` is the `2^h`-th predecessor of `u` +// according to the `parent` relation. +void compute_pred(const vector& parent, vvi& pred) { + const int H = pred.size() - 1; + const int n = parent.size(); + + for (int u = 0; u < n; ++u) + pred[0][u] = parent[u]; + + for (int h = 1; h <= H; ++h) + for (int u = 0; u < n; ++u) { + assert(0 <= pred[h-1][u] && pred[h-1][u] < n); + pred[h][u] = pred[h-1][ pred[h-1][u] ]; + } +} + + +iii lca(const vector>& pred, const vector& depth, int u, int v) { + const int H = pred.size() - 1; + + if (u == v) + return {u, {-1, -1}}; + + if (depth[u] < depth[v]) { + auto [w, ij] = lca(pred, depth, v, u); + auto [i, j] = ij; + return {w, {j, i}}; + } + + if (depth[u] != depth[v]) { + for (int h = H; h >= 0; h--) + if (depth[ pred[h][u] ] > depth[v]) + u = pred[h][u]; + + assert(depth[pred[0][u]] == depth[v]); + + if (pred[0][u] == v) + return { v, { u, -1} }; + + u =pred[0][u]; + } + + assert(depth[u] == depth[v]); + assert(u != v); + + for (int h = H; h >= 0; --h) { + if (pred[h][u] != pred[h][v]) { + u = pred[h][u]; + v = pred[h][v]; + } + } + + assert(pred[0][u] == pred[0][v]); + return { pred[0][u], { u, v } }; +} + +int main() { + int subtask; + scanf("%i", &subtask); + + int n, q; + scanf("%i%i", &n, &q); + + vector tip(n); + for (int i = 0; i < n; ++i) + scanf("%i", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + vvii tree(n, vector{}); + for (int i = 0; i < n-1; ++i) { + int u, v, w; + scanf("%i%i%i", &u, &v, &w); + assert(1 <= u && u <= n); + assert(1 <= v && v <= n); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + debug("Read the tree\n"); + + // "Hang" the tree from node 0 and compute the depth of each node. + vector depth(n, 0), parent(n, 0), parent_weight(n, 0); + debug("Created vectors\n"); + preprocess(tree, 0, depth, parent, parent_weight); + debug("preprocessed\n"); + + int max_depth = 0; + for (int i = 0; i < n; ++i) + max_depth = max(max_depth, depth[i]); + + // Using the parent relation, compute `pred[h][u]`, the `2^h`-th predecessor + // of each node `u` for every `h \in {0, 1, ..., ceil(log_2(n-1))}`. + // const int H = int(ceil(log2(n-1))); + const int H = int(ceil(log2(max_depth))); + vector> pred(H+1, vector(n, 0)); + compute_pred(parent, pred); + debug("computed pred\n"); + + vector subtree_loop_opt(n), supertree_root_opt(n), supertree_loop_opt(n); + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, 0, -1); + debug("done with pass1\n"); + compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, 0, -1, -1); + debug("done with pass2\n"); + compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, 0, -1, -1); + debug("done with pass3\n"); + + for (int i = 0; i < q; ++i) { + int src, dst; + scanf("%i%i", &src, &dst); + src -= 1; + dst -= 1; + + if (src == dst) { + printf("%li\n", subtree_loop_opt[src] + supertree_loop_opt[src]); + continue; + } + + auto [mid, uv] = lca(pred, depth, src, dst); + auto [u, v] = uv; + assert(u != -1 || v != -1); + debug("LCA(%i, %i) = (%i, (%i, %i))\n", src+1, dst+1, mid+1, u+1, v+1); + + long sol = 0; + if (u == -1) { + // src is an ancestor of dst. + assert(mid == src); + sol = supertree_root_opt[dst] - supertree_root_opt[src] + supertree_loop_opt[src] + subtree_loop_opt[dst]; + } else if (v == -1) { + // dst is an ancestor of src. + assert(mid == dst); + sol = supertree_root_opt[src] - supertree_root_opt[dst] + supertree_loop_opt[dst] + subtree_loop_opt[src]; + } else { + // `src` and `dst` have a common ancestor. + assert(pred[0][u] == mid); + assert(pred[0][v] == mid); + + sol = supertree_root_opt[src] + subtree_loop_opt[src] - supertree_root_opt[u]; + debug("%li, ", sol); + sol += supertree_root_opt[dst] + subtree_loop_opt[dst] - supertree_root_opt[v]; + debug("%li, ", sol); + sol -= (parent_weight[u] + parent_weight[v]); + debug("%li, ", sol); + + sol += subtree_loop_opt[mid]; + debug("%li, ", sol); + sol -= positive_part(subtree_loop_opt[u] - 2*parent_weight[u]); + debug("%li, ", sol); + sol -= positive_part(subtree_loop_opt[v] - 2*parent_weight[v]); + debug("%li, ", sol); + + sol += supertree_loop_opt[mid]; + debug("%li, ", sol); + debug("\n"); + } + + printf("%li\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..ad2117db --- /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); + + int n, q; + scanf("%i%i", &n, &q); + + long sol = 0; + for (int i = 0; i < n; ++i) { + int t; + scanf("%i", &t); + sol += t; + } + + for (int i = 0; i < n-1; ++i) { + int tmp1, tmp2, tmp3; + scanf("%i%i%i", &tmp1, &tmp2, &tmp3); + } + + for (int i = 0; i < q; ++i) { + int src, dst; + scanf("%i%i", &src, &dst); + printf("%li\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..706b31c3 --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc @@ -0,0 +1,65 @@ +#include +#include +#include + +using namespace std; + +using ii = pair; +using vvii = vector>; + +long positive_part(long x) { return max(0L, x); } + +// Επιστρέφει το κέρδος της βέλτιστης διαδρομή η οποία ξεκινάει +// από την κορυφή `u`, καταλλήγει πίσω σε αυτή και παραμένει +// εξ' ολοκλήρου μέσα στο υποδέντρο που ορίζει η `u` -- με άλλα λόγια, +// η διαδρομή απαγορεύεται να διασχίσει το δρόμο `(u, parent)`. +long subtree_loop_opt(const vvii& tree, const vector& tip, int u, int parent) { + long sol = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + long s = subtree_loop_opt(tree, tip, v, u); + sol += positive_part(s - 2*w); + } + + return sol; +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 2); + + int n, q; + scanf("%i%i", &n, &q); + assert(1 <= n && n <= 1'000); + assert(1 <= q && q <= 1'000); + + vector tip(n); + for (int i = 0; i < n; ++i) + scanf("%i", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + vvii tree(n, vector{}); + for (int i = 0; i < n-1; ++i) { + int u, v, w; + scanf("%i%i%i", &u, &v, &w); + assert(1 <= u && u <= n); + assert(1 <= v && v <= n); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + for (int i = 0; i < q; ++i) { + int src, dst; + scanf("%i%i", &src, &dst); + assert(src == dst); + + printf("%li\n", subtree_loop_opt(tree, tip, src-1, -1)); + } + + 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..998f8627 --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc @@ -0,0 +1,91 @@ +#include +#include +#include + +using namespace std; + +using ii = pair; +using vvii = vector>; + +long positive_part(long x) { return max(0L, x); } + +// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// κι όλους τους απογόνους της. +// +// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// τον δρόμο `(u, parent)`. +void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { + subtree_loop_opt[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + } +} + +// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή +// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// +// supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και +// μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής +// `u` ΔΕΝ προσμετράται. +void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { + assert(0 <= u && u < tree.size()); + assert(-1 <= parent && parent < (long)tree.size()); + + supertree_root_opt[u] = 0; + + if (parent != -1) + supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, v, u, w); +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 2 || subtask == 3); + + int n, q; + scanf("%i%i", &n, &q); + + vector tip(n); + for (int i = 0; i < n; ++i) + scanf("%i", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + vvii tree(n, vector{}); + for (int i = 0; i < n-1; ++i) { + int u, v, w; + scanf("%i%i%i", &u, &v, &w); + assert(1 <= u && u <= n); + assert(1 <= v && v <= n); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + for (int i = 0; i < q; ++i) { + int src, dst; + scanf("%i%i", &src, &dst); + src -= 1; + dst -= 1; + + vector subtree_loop_opt(n), supertree_root_opt(n); + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, dst, -1); + compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, dst, -1, -1); + + printf("%li\n", subtree_loop_opt[src] + supertree_root_opt[src]); + } + + 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..01d02f86 --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc @@ -0,0 +1,90 @@ +#include +#include +#include + +using namespace std; + +using ii = pair; +using vvii = vector>; + +long positive_part(long x) { return max(0L, x); } + +// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// κι όλους τους απογόνους της. +// +// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// τον δρόμο `(u, parent)`. +void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { + subtree_loop_opt[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + } +} + +// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_loop_opt` για την κορυφή +// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// +// supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά +// ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που +// ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. +void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { + assert(0 <= u && u < tree.size()); + assert(-1 <= parent && parent < (long)tree.size()); + + supertree_loop_opt[u] = 0; + + if (parent != -1) + supertree_loop_opt[u] = positive_part(subtree_loop_opt[parent] + supertree_loop_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - 2*w); + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, v, u, w); +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 2 || subtask == 4); + + int n, q; + scanf("%i%i", &n, &q); + + vector tip(n); + for (int i = 0; i < n; ++i) + scanf("%i", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + vvii tree(n, vector{}); + for (int i = 0; i < n-1; ++i) { + int u, v, w; + scanf("%i%i%i", &u, &v, &w); + assert(1 <= u && u <= n); + assert(1 <= v && v <= n); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + vector subtree_loop_opt(n); + vector supertree_loop_opt(n); + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, 0, -1); + compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, 0, -1, -1); + + for (int i = 0; i < q; ++i) { + int src, dst; + scanf("%i%i", &src, &dst); + assert(src == dst); + + printf("%li\n", subtree_loop_opt[src-1] + supertree_loop_opt[src-1]); + } + + 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..69539654 --- /dev/null +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc @@ -0,0 +1,98 @@ +#include +#include +#include + +using namespace std; + +using ii = pair; +using vvii = vector>; + +long positive_part(long x) { return max(0L, x); } + +// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// κι όλους τους απογόνους της. +// +// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// τον δρόμο `(u, parent)`. +void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { + subtree_loop_opt[u] = tip[u]; + + for (auto [v, w]: tree[u]) { + if (v == parent) continue; + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + } +} + +// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή +// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// +// supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και +// μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής +// `u` ΔΕΝ προσμετράται. +void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { + assert(0 <= u && u < tree.size()); + assert(-1 <= parent && parent < (long)tree.size()); + + supertree_root_opt[u] = 0; + + if (parent != -1) + supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; + + for (auto [v, w]: tree[u]) + if (v != parent) + compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, v, u, w); +} + +int main() { + int subtask; + scanf("%i", &subtask); + assert(subtask == 5); + + int n, q; + scanf("%i%i", &n, &q); + + vector tip(n); + for (int i = 0; i < n; ++i) + scanf("%i", &tip[i]); + + // Αναπαράσταση του δέντρου με adjacency list: + // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που + // συνδέεται με τη `u` με κόστός `w`. + vvii tree(n, vector{}); + for (int i = 0; i < n-1; ++i) { + int u, v, w; + scanf("%i%i%i", &u, &v, &w); + assert(1 <= u && u <= n); + assert(1 <= v && v <= n); + + tree[u-1].push_back({v-1, w}); + tree[v-1].push_back({u-1, w}); + } + + int src, dst; + scanf("%i%i", &src, &dst); + src -= 1; + dst -= 1; + + vector subtree_loop_opt(n), supertree_root_opt(n); + compute_subtree_loop_opt(subtree_loop_opt, tree, tip, src, -1); + compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, src, -1, -1); + + // Απάντηση στο πρώτο ερώτημα. + printf("%li\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); + + // Απάντηση στα υπόλοιπα `q-1` ερωτήματα. + for (int i = 1; i < q; ++i) { + scanf("%i%i", &src, &dst); + src -= 1; + dst -= 1; + printf("%li\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); + } + + 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..f5cde0df --- /dev/null +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -0,0 +1,336 @@ +--- +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$$ των φιλοδωρημάτων. + +## Γενικές Παρατηρήσεις + +Πριν προχωρήσουμε, ας παρατηρήσουμε κάποια πράγματα για την βέλτιστη λύση που +θα μας φανούν χρήσιμα σε όλα τα υποπροβλήματα που ακολουθούν. Αρχικά, στη +βέλτιστη λύση δεν θα χρειαστεί ποτέ να διασχίσουμε κάποιο δρόμο πάνω από δύο +φορές (μπορείτε να δείτε γιατί;). + +Και γενικότερα, η βέλτιστη λύση θα είναι μια διαδρομή η οποία θα περιλαμβάνει +το (μοναδικό) μονοπάτι που συνδέει τα σπίτια $$L$$ και $$R$$, όπου σε κάθε +στάση σε κάποιο σπίτι $$u$$, θα έχουμε την ευκαιρία να "ξεφύγουμε" για λίγο από +το βασικό του μονοπάτι και να εξερευνήσουμε τα σπίτια που συνδέεονται με το +$$u$$ (άμεσα ή έμεσα μέσω άλλων δρόμων), να μαζέψουμε όσα περισσότερα +φιλοδωρήματα μπορούμε, να επιστρέψουμε πάλι στο $$u$$ και να συνεχίσουμε προς +τον τελικό μας προορισμό. Έτσι, στο τέλος θα έχουμε διασχίσει τους δρόμους που +βρίσκονται στο κεντρικό μονοπάτι μεταξύ $$L$$ και $$R$$ μόνο μια φορά, και για +κάθε άλλο δρόμο θα τον έχουμε διασχίσει είτε καμία είτε δύο φορές. + +## Υποπρόβλημα 2 ($$N \le 1.000, Q \le 1.000, L = R$$) --- Λύση $$\mathcal{O}(N Q)$$ + +Ο περιορισμός $$L = R$$ απλουστεύει το πρόβλημα καθώς δεν χρειάζεται να +ασχοληθούμε με το μέρος της λύσης που περιλαμβάνει το μονοπάτι που αναφέραμε +προηγουμένως, παρά μόνο με την επιμέρους εξερεύνηση. + +Ας φανταστούμε ότι το σπίτι $$L$$ από το οποία ξεκινάμε είναι η ρίζα του +δέντρου που αναπαριστά το οδικό δίκτυο. Κάθε σπίτι $$u$$ που συνδέεται άμεσα με +το $$L$$ ορίζει ένα _υποδέντρο_ το οποίο θα συμβολίζουμε με +$$\text{subtree}(u)$$. Για κάθε ένα από αυτά τα δέντρα, έχουμε να κάνουμε μια +ανεξάρτητη επιλογή: +1. _να διασχίσουμε_ το δρόμο $$L \rightarrow u$$ κόστους $$w$$, να μαζέψουμε + όσα περισσότερα φιλοδωρήματα μπορούμε από το $$\text{subtree}(u)$$ και να + επιστρέψουμε πίσω διασχίζοντας το δρόμο $$u \rightarrow L$$ για δεύτερη + φορά, ή +2. να _μη διασχίσουμε ποτέ_ το δρόμο που συνδέει το σπίτι $$L$$ με το σπίτι + $$u$$. + +Ας συμβολίσουμε με $$\text{subtree\_loop\_opt}[u]$$ το μέγιστό δυνατό κέρδος +που μπορούμε να έχουμε αν ξεκινήσουμε από το σπίτι $$u$$, κινούμαστε μόνο μέσα +στο υποδέντρο που ορίζει το σπίτι $$u$$, και τελικά επιστρέψουμε πάλι στο +$$u$$. + +Παρατηρήστε ότι η επιλογή 1 είναι προτιμότερη όταν το κέρδος της είναι +θετικό, δηλαδή όταν $$\text{subtree\_loop\_opt}[u] - 2 \cdot w \ge 0$$. +Διαφορετικά η επιλογή 2 είναι καλύτερη --- ή ισοδύναμη σε περίπτωση μηδενικού +κέρδους. + + +Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{subtree\_loop\_opt}$$ +αναδρομικά εφαρμόζοντας τον παρακάτω τύπο: + +$$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} +\left( \text{subtree\_loop\_opt}[v] - 2 \cdot w_{u, v} \right)^+ $$ + +όπου ο συμβολισμός $$(x)^+$$ σημαίνει $$\max(0, x)$$. + + +Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{subree\_loop\_opt}[L]$$. + +Για τον υπολογισμό όλων των παραπάνω τιμών, χρειάζεται για κάθε κορυφή να +υπολογίσουμε ένα άθροισμα που περιλαμβάνει όλα τα παιδιά της, συνεπώς +χρειάζεται χρόνος γραμμικός στο πλήθος των κορυφών και των ακμών του δέντρου, +άρα $$\mathcal{O}(N)$$ για κάθε ερώτημα. Συνολικά η λύση αυτή έχει +πολυπλοκότητα $$\mathcal{O}(N Q)$$, η οποία μας καλύπτει για τους περιορισμούς +αυτού του υποπροβλήματος. + +## Υποπρόβλημα 3 ($$N \le 1.000, Q \le 1.000$$) --- Λύση $$\mathcal{O}(N Q)$$ + +Ας σκεφτόυμε τώρα την πιο γενική περίπτωση όπου η διαδρομή μας θα πρέπει να +καταλήγει σε κάποιο σπίτι $$R$$, πιθανώς διαφορετικό του $$L$$. Μπορούμε να +ξεκινήσουμε παρόμοια με πριν, θεωρώντας αυτή τη φορά ότι η κορυφή $$R$$ είναι η +ρίζα του δέντρου. Η αφετηρία $$L$$ θα βρίσκεται σε κάποιο μικρότερο υποδέντρο. +Ξεκινώντας από την $$L$$, είμαστε ελεύθεροι να εξερευνήσουμε το υποδέντρο +$$\text{subtree}(L)$$ για το οποίο μπορούμε να υπολογίσουμε τη βέλτιστη +διαδρομή χρησιμοποιώντας το $$\text{subtree\_loop\_opt}[L]$$ που ορίσαμε +προηγουμένως. + +Σε αντίθεση όμως με πριν, μόλις επιστρέψουμε στο $$L$$ θα πρέπει να ανέβουμε +ένα επίπεδο παραπάνω, ακολουθώντας το μονοπάτι προς τη ρίζα και μαζεύοντας κι +άλλα φιλοδωρήματα στην πορεία. Ας ορίσουμε λοιπόν την ποσότητα +$$\text{supertree\_root\_opt}[u]$$ η οποία θα είναι ίση με το μέγιστο κέρδος που +μπορούμε να έχουμε όταν ξεκινάμε από το σπίτι $$u$$ (χωρίς όμως να συλλέγουμε +το φιλοδώρημα του $$u$$), καταλλήγουμε τελικά στη ρίζα $$R$$ του δέντρου, και +σε όλη τη διαδρομή απαγορεύεται να επισκεφτούμε οποιοδήποτε σπίτι βρίσκεται στο +υποδέντρο $$\text{subtree}(u)$$. + +Εαν υπολογίσουμε την τιμή της παραπάνω ποσότητας για την κορυφή $$L$$, τότε +μπορούμε να υπολογίσουμε την απάντηση του ερωτήματος απλά με το άθροισμα: + +$$ \text{subtree\_loop\_opt}[L] + \text{supertree\_root\_opt}[L] $$. + +Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{supertree\_root\_opt}[u]$$ +από "πάνω προς τα κάτω" χρησιμοποιώντας τις τιμές $$\text{subtree\_loop\_opt}$$ +ως εξής. Αν η κορυφή $$u$$ είναι η ρίζα, τότε εξ ορισμού $$\text{supertree\_root\_opt}[u] = 0$$. +Διαφορετικά, + +$$ +\begin{align*} + \text{supertree\_root\_opt}[u] =& + \text{supertree\_root\_opt}[\text{parent}(u)] + + \text{subtree\_loop\_opt}[\text{parent}(u)] \\ + &- (\text{subtree\_loop\_opt}[u] - 2 \cdot w_{u, \text{parent}(u)})^+ + - w_{u, \text{parent}(u)} +\end{align*} +$$ + +Όντως, ο παραπάνω τύπος χρησιμοποιεί την τιμή του +$$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ του $$u$$, +που μπορούμε να θεωρήσουμε ότι έχουμε ήδη υπολογίσει (θυμηθείτε ότι +υπολογίζουμε τις τιμές από πάνω προς τα κάτω), η οποία περιλαμβάνει τη βέλτιστη +λύση που αφορά τη διαδρομή από $$\text{parent}(u)$$ μέχρι τη ρίζα. Το μόνο που +μένει είναι να συμπεριλάβουμε το κομμάτι της διαδρομής που εξερευνά τα σπίτια +που συνδέονται στο $$u$$ και στον γονέα του. Η τιμή για αυτό το κομμάτι της +διαδρομής είναι _σχεδόν_ η τιμή $$\text{subtree\_loop\_opt}[\text{parent}(u)]$$ +που είχαμε ορίσει προηγουμένως, με τη διαφορά ότι αυτή τιμή πιθανώς να +περιλαμβάνει το κόστος της ακμής $$(u, \text{parent}[u])$$ _δύο φορές_. Πρέπει +λοιπόν να διορθώσουμε την τιμή, αφαιρώντας την κατάλληλη ποσότητα, μόνο όμως +όταν αυτή έχει θετικό πρόσημο. Τέλος, σε κάθε περίπτωση, συμπεριλαμβάνουμε και +το κόστος διάσχισης του δρόμου $$(u, \text{parent}[u])$$ ακριβώς μία φορά. + +Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διαπεράσεις_ του +δέντρου (μια για τον υπογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα μια +για τον υπολογισμό των $$\text{supertree\_root\_opt}$$), επομένως +$$\mathcal{O}(N)$$ χρόνος. Συνολικά $$\mathcal{O}(N Q)$$ πολυπλοκότητα και γι +αυτό το υποπρόβλημα. + +## Υποπρόβλημα 4 ($$L = R$$) --- Λύση $$\mathcal{O}(N + Q)$$ + +Σε αυτό το υποπρόβλημα το μέγεθος του δέντρου και το πλήθος των ερωτημάτων +είναι αρκετά μεγαλύτερο και έτσι η λύση που περιγράψαμε νωρίτερα για το +υποπρόβλημα 2 δεν επαρκεί. Θα πρέπει να βρούμε κάποιο τρόπο να απαντάμε τα +ερωτήματα χωρίς να διαπερνάμε όλο το δέντρο από την αρχή κάθε φορά. Ο λόγος που +χρειαζόταν να διαπεράσουμε το δέντρο από την αρχή σε κάθε ερώτημα, ήταν ότι +έπρεπε να αλλάξουμε ρίζα, κι έτσι οι τιμές που είχαμε για το +$$\text{subtree\_loop\_opt}$$ δεν θα ήταν έγκυρες για το δέντρο του επόμενου +ερωτήματος. Παρ' όλα αυτά, ας προσπαθήσουμε να σκεφτούμε αν μπορούμε με κάποιο +τρόπο να τις εκμεταλλευτούμε ώστε να απαντήσουμε μελλοντικά ερωτήματα. + +Ας θεωρήσουμε λοιπόν στο εξής ότι η ρίζα του δέντρου είναι πάντα η κορυφή 1, κι +ας υπολογίσουμε τις τιμές $$\text{subtree\_loop\_opt}$$ όπως τις ορίσαμε +παραπάνω. Η απάντηση ένα ερώτημα στο οποίο $$L = R \ne 1$$, είναι _σχεδόν_ ίση +με $$\text{subtree\_loop\_opt}[L]$$, με τη διαφορά ότι αυτή η τιμή δεν +περιλαμβάνει διαδρομές που ανεβαίνουν σε υψηλότερα επίπεδα του δέντρου και +γυρνάνε πάλι πίσω στην κορυφή $$L$$. Αυτό όμως διορθώνεται εύκολα αν +προ-υπολογίσουμε για κάθε κορυφή $$u$$ αυτή την τιμή, ας την ονομάσουμε +$$\text{supertree\_loop\_opt}[u]$$, ώστε να την έχουμε διαθέσιμη όταν τη +χρειαστούμε για να απαντήσουμε κάποιο ερώτημα. + +Μάλιστα, οι τιμές αυτές μοιάζουν πολύ με τις τιμές $$\text{supertree\_root\_opt}$$ που +ορίσαμε στο προηγούμενο υποπρόβλημα, με τη διαφορά όμως ότι δεν είναι πλέον αναγκαίο +να φτάσουμε μέχρι τη ρίζα, αλλά πρέπει να γυρίσουμε πίσω στην κορυφή $$u$$. Ο τύπος +που είχαμε προηγούμενως αλλάζει ως εξής: + +$$ +\begin{align*} +\text{supertree\_loop\_opt}[u] = + ( & \text{supertree\_loop\_opt}[\text{parent}(u)] + + \text{subtree\_loop\_opt}[\text{parent}(u)] \\ + &- (\text{subtree\_loop\_opt}[u] - 2 \cdot w_{u, \text{parent}(u)})^+ + - 2 \cdot w_{u, \text{parent}(u)} )^+ +\end{align*} +$$ + +Ο προ-υπολογισμός των $$\text{subtree\_loop\_opt}$$ και +$$\text{supertree\_loop\_opt}$$ με ρίζα την κορυφή 1 μπορεί να γίνει με δύο +διαπεράσεις του δέντρου σε χρόνο $$\mathcal{O}(N)$$ πριν ξεκινήσουμε να απαντάμε +ερωτήματα, κι έπειτα για κάθε ερώτημα, μπορούμε να υπολογίσουμε την απάντηση σε +σταθερό χρόνο με τον τύπο: + +$$\text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]$$. + +Συνολικά η λύση αυτή έχει χρονική πολυπλοκότητα $$\mathcal{O}(N + Q)$$. + +## Υποπρόβλημα 5 ($$L_1 = L_2 = \ldots = L_N$$) --- Λύση $$\mathcal{O}(Ν + Q)$$ + +Σε αυτή την περίπτωση η αφετηρία και ο προορισμός μπορεί να είναι διαφορετικά +σπίτια, όμως η εκφώνηση μας εξασφαλίζει ότι οι αφετηρίες όλων των ερωτημάτων +θα είναι οι ίδιες. Αυτό μας επιτρέπει να θέσουμε την κοινή κορυφή $$L$$ ως ρίζα +και να εφαρμόσουμε τη λύση του υποπροβλήματος 3, υπολογίζοντας όμως +τις βοηθητικές τιμές $$\text{subtree\_loop\_opt}$$ και $$\text{supertree\_root\_opt}$$ +μόνο μια φορά στην αρχή, και απαντώντας μετά το κάθε ερώτημα σε σταθερό χρόνο. + +**Σημείωση**: Στο υποπρόβλημα 3 θεωρήσαμε ότι η ρίζα ήταν το $$R$$, όμως λόγω +συμμετρίας του προβλήματος, θα μπορούσαμε να είχαμε θεωρήσει ως ρίζα το $$L$$ όπως +κάνουμε σε αυτό το υποπρόβλημα. + +## Υποπρόβλημα 6 --- Βέλτιστη Λύση $$\mathcal{O}((N + Q) \log N)$$ + +Ας προσπαθήσουμε να γενικεύσουμε την προηγούμενη λύση στην περίπτωση που +η ρίζα του δέντρου με βάση την οποία προ-υπολογίσαμε τις βοηθητικές +τιμές δεν είναι ούτε η αφετηρία ούτε ο προορισμός. Θα ξεκινήσουμε περιγράφοντας +τη μορφή της βέλτιστης διαδρομής, κι έπειτα θα δούμε πώς να υπολογίσουμε +το κέρδος της σε **λογαριθμικό χρόνο** ως προς το ύψος του δέντρου. + +Το μονοπάτι που συνδέει τις κορυφές $$L$$ και $$R$$ θα έχει την εξής μορφή: +ξεκινώντας από την $$L$$ ανεβαίνουμε (0 ή παραπάνω) επίπεδα μέχρι να φτάσουμε +στον Ελάχιστο Κοινό Πρόγονο (Lowest Common Ancestor) των $$L$$ και $$R$$, +τον οποίο θα συμβολίζουμε με $$\text{LCA}(L, R)$$, κι έπειτα κινούμαστε προς χαμηλότερα +επίπεδα μέχρι να φτάσουμε στην κορυφή $$R$$. Από κάθε κόμβο αυτού του μονοπατιού, +μπορούμε να εξερευνήσουμε υποδέντρα ώστε να μαζέψουμε φιλοδωρήματα (ακριβώς όπως +στον ορισμό του $$\text{subtree\_loop\_opt}$$). Επιπλέον, από +την κορυφή $$\text{LCA}(L, R)$$ έχουμε τη δυνατότητα να ακολουθήσουμε μια +κυκλική διαδρομή που επισκέπτεται υψηλότερα επίπεδα του δέντρου (ακριβώς +όπως στον ορισμό του $$\text{supertree\_loop\_opt}$$ παραπάνω). + +Για την ώρα, ας υποθέσουμε ότι γνωρίζουμε τον ελάχιστο κοινό πρόγονο $$z = +LCA(L, R)$$ --- θα δούμε σύντομα πώς να τον υπολογίσουμε σε χρόνο +$$\mathcal{O}(\log N)$$ με κατάλληλη προεργασία. + +Έχουμε τις παρακάτω περιπτώσεις: + +- $$z = L$$: Η περίπτωση αυτή μοιάζει πολύ με το υποπρόβλημα 5, με τη διαφορά + ότι αφετηρία δεν είναι η ρίζα του δέντρου και συνεπώς αν εφαρμόσουμε τον τύπο + $$\text{subtree\_loop\_opt}[R] + \text{supertree\_root\_opt}[R]$$ θα πάρουμε + λάθος απάντηση καθώς ο δεύτερος όρος περιέχει το κέρδος της λύσης που + ανεβαίνει μέχρι τη ρίζα αντί αυτής που καταλήγει στην κορυφή $$z$$. Αυτό όμως + διορθώνεται εύκολα αν **αφαιρέσουμε** το κερδος που αντιστοιχεί στο κομμάτι + της διαδρομής που ξεκινάει από την κορυφή $$z$$ και φτάνει στη ρίζα (το + έχουμε ήδη υπολογίσει, είναι το $$\text{supertree\_root\_opt}(z)$$) και αντ' + αυτού **προσθέσουμε** το κέρδος της _κυκλικής_ διαδρομής που ξεκινάει και + επιστρέφει στο $$z$$, μαζεύοντας φιλοδωρήματα από κόμβους εκτός του + $$\text{subtree}(z)$$ (κι αυτή η τιμή είναι διαθέσιμη στο + $$\text{supertree\_loop\_opt}(z)$$). Συνοψίζοντας, η + απάντηση σε αυτή την περίπτωση είναι: + + $$ \text{supertree\_root\_opt}(L) + \text{subtree\_loop\_opt}(R) + -\text{supertree\_root\_opt}(z) + \text{supertree\_loop\_opt}(z) $$ + +- $$z = L$$: Αντίστοιχα με την προηγούμενη περίπτωση, η απάντηση είναι: + + $$ \text{supertree\_root\_opt}(R) + \text{subtree\_loop\_opt}(L) + -\text{supertree\_root\_opt}(z) + \text{supertree\_loop\_opt}(z) $$ + +- $$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{supertree\_root\_opt}[L] + - \text{supertree\_root\_opt}[u] - \text{subtree\_loop\_opt}[L])}_{(a)} \\ + +&\underbrace{(\text{supertree\_root\_opt}[R] + - \text{supertree\_root\_opt}[v] - \text{subtree\_loop\_opt}[R])}_{(b)} \\ + +&\underbrace{( + \text{subtree\_loop\_opt}[z] + - (\text{subtree\_loop\_opt}[u] - 2\cdot w_{u, z})^+ + - (\text{subtree\_loop\_opt}[v] - 2\cdot w_{v, z})^+) + }_{(c)} \\ + +&\underbrace{\text{supertree\_loop\_opt}[z]}_{(d)} \\ + -&\underbrace{(w_{u, z} + w_{v, z})}_{(e)} + \end{align*} + $$ + +Είδαμε λοιπόν πώς να απαντάμε τα ερωτήματα γρήγορα (και μάλιστα σε σταθερό +χρόνο), αρκεί να μπορούμε να υπολογίσουμε τον ελάχιστο κοινό πρόγονο μαζί με +τις κορυφές $$u, v$$ όπως τις ορίσαμε παραπάνω. Θα δούμε τώρα πώς μπορούμε να +το κάνουμε αυτό χρησιμοποιώντας την μέθοδο του "sparse table" σε χρόνο +$$\mathcal{O}(\log N)$$ για κάθε ερώτημα (στην πραγματικότητα σε χρόνο +$$\mathcal{O}(\log h)$$ όπου $$h$$ το ύψος του δέντρου από την ρίζα που +επιλέξαμε στην αρχή), και με προεργασία σε χρόνο $$\mathcal{O}(N \log N)$$. + +**TODO**: LCA From c460b4e5bcf83d6433ea21e7585eab98275e35a8 Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Wed, 19 Mar 2025 22:22:36 -0700 Subject: [PATCH 02/16] Remove some runtime claims for now --- contests/_37-PDP/b-tiphunting-solution.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/contests/_37-PDP/b-tiphunting-solution.md b/contests/_37-PDP/b-tiphunting-solution.md index f5cde0df..9bb1e346 100644 --- a/contests/_37-PDP/b-tiphunting-solution.md +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -235,13 +235,13 @@ $$\text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]$$. συμμετρίας του προβλήματος, θα μπορούσαμε να είχαμε θεωρήσει ως ρίζα το $$L$$ όπως κάνουμε σε αυτό το υποπρόβλημα. -## Υποπρόβλημα 6 --- Βέλτιστη Λύση $$\mathcal{O}((N + Q) \log N)$$ +## Υποπρόβλημα 6 Ας προσπαθήσουμε να γενικεύσουμε την προηγούμενη λύση στην περίπτωση που η ρίζα του δέντρου με βάση την οποία προ-υπολογίσαμε τις βοηθητικές τιμές δεν είναι ούτε η αφετηρία ούτε ο προορισμός. Θα ξεκινήσουμε περιγράφοντας τη μορφή της βέλτιστης διαδρομής, κι έπειτα θα δούμε πώς να υπολογίσουμε -το κέρδος της σε **λογαριθμικό χρόνο** ως προς το ύψος του δέντρου. +το κέρδος της σε υπο-γραμμικό χρόνο. Το μονοπάτι που συνδέει τις κορυφές $$L$$ και $$R$$ θα έχει την εξής μορφή: ξεκινώντας από την $$L$$ ανεβαίνουμε (0 ή παραπάνω) επίπεδα μέχρι να φτάσουμε @@ -255,8 +255,8 @@ $$\text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]$$. όπως στον ορισμό του $$\text{supertree\_loop\_opt}$$ παραπάνω). Για την ώρα, ας υποθέσουμε ότι γνωρίζουμε τον ελάχιστο κοινό πρόγονο $$z = -LCA(L, R)$$ --- θα δούμε σύντομα πώς να τον υπολογίσουμε σε χρόνο -$$\mathcal{O}(\log N)$$ με κατάλληλη προεργασία. +LCA(L, R)$$ --- θα δούμε σύντομα πώς να τον υπολογίσουμε αποδοτικά με κατάλληλη +προεργασία. Έχουμε τις παρακάτω περιπτώσεις: @@ -327,10 +327,6 @@ $$\mathcal{O}(\log N)$$ με κατάλληλη προεργασία. Είδαμε λοιπόν πώς να απαντάμε τα ερωτήματα γρήγορα (και μάλιστα σε σταθερό χρόνο), αρκεί να μπορούμε να υπολογίσουμε τον ελάχιστο κοινό πρόγονο μαζί με -τις κορυφές $$u, v$$ όπως τις ορίσαμε παραπάνω. Θα δούμε τώρα πώς μπορούμε να -το κάνουμε αυτό χρησιμοποιώντας την μέθοδο του "sparse table" σε χρόνο -$$\mathcal{O}(\log N)$$ για κάθε ερώτημα (στην πραγματικότητα σε χρόνο -$$\mathcal{O}(\log h)$$ όπου $$h$$ το ύψος του δέντρου από την ρίζα που -επιλέξαμε στην αρχή), και με προεργασία σε χρόνο $$\mathcal{O}(N \log N)$$. +τις κορυφές $$u, v$$ όπως τις ορίσαμε παραπάνω. **TODO**: LCA From d54389a5d98f26a890587d6ad64f8aa175ad8191 Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Wed, 19 Mar 2025 22:44:44 -0700 Subject: [PATCH 03/16] Fix typos --- contests/_37-PDP/b-tiphunting-solution.md | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/contests/_37-PDP/b-tiphunting-solution.md b/contests/_37-PDP/b-tiphunting-solution.md index 9bb1e346..3c833233 100644 --- a/contests/_37-PDP/b-tiphunting-solution.md +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -53,16 +53,16 @@ _διαδρομή_ ορίζουμε μια πεπερασμένη ακολουθ ## Γενικές Παρατηρήσεις -Πριν προχωρήσουμε, ας παρατηρήσουμε κάποια πράγματα για την βέλτιστη λύση που -θα μας φανούν χρήσιμα σε όλα τα υποπροβλήματα που ακολουθούν. Αρχικά, στη +Πριν προχωρήσουμε, ας παρατηρήσουμε κάποιες ιδιότητες της βέλτιστης λύσης που +θα μας φανούν χρήσιμα σε όλα τα υποπροβλήματα που ακολουθούν. Αρχικά, στη βέλτιστη λύση δεν θα χρειαστεί ποτέ να διασχίσουμε κάποιο δρόμο πάνω από δύο φορές (μπορείτε να δείτε γιατί;). -Και γενικότερα, η βέλτιστη λύση θα είναι μια διαδρομή η οποία θα περιλαμβάνει +Γενικότερα, η βέλτιστη λύση θα είναι μια διαδρομή η οποία θα περιλαμβάνει το (μοναδικό) μονοπάτι που συνδέει τα σπίτια $$L$$ και $$R$$, όπου σε κάθε στάση σε κάποιο σπίτι $$u$$, θα έχουμε την ευκαιρία να "ξεφύγουμε" για λίγο από -το βασικό του μονοπάτι και να εξερευνήσουμε τα σπίτια που συνδέεονται με το -$$u$$ (άμεσα ή έμεσα μέσω άλλων δρόμων), να μαζέψουμε όσα περισσότερα +το βασικό του μονοπάτι και να εξερευνήσουμε τα σπίτια που συνδέονται με το +$$u$$ (άμεσα ή έμμεσα μέσω άλλων δρόμων), να μαζέψουμε όσα περισσότερα φιλοδωρήματα μπορούμε, να επιστρέψουμε πάλι στο $$u$$ και να συνεχίσουμε προς τον τελικό μας προορισμό. Έτσι, στο τέλος θα έχουμε διασχίσει τους δρόμους που βρίσκονται στο κεντρικό μονοπάτι μεταξύ $$L$$ και $$R$$ μόνο μια φορά, και για @@ -140,10 +140,10 @@ $$\text{supertree\_root\_opt}[u]$$ η οποία θα είναι ίση με τ $$ \text{subtree\_loop\_opt}[L] + \text{supertree\_root\_opt}[L] $$. -Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{supertree\_root\_opt}[u]$$ -από "πάνω προς τα κάτω" χρησιμοποιώντας τις τιμές $$\text{subtree\_loop\_opt}$$ -ως εξής. Αν η κορυφή $$u$$ είναι η ρίζα, τότε εξ ορισμού $$\text{supertree\_root\_opt}[u] = 0$$. -Διαφορετικά, +Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{supertree\_root\_opt}[u]$$ από +"πάνω προς τα κάτω" χρησιμοποιώντας τις τιμές $$\text{subtree\_loop\_opt}$$ ως +εξής: αν η κορυφή $$u$$ είναι η ρίζα, τότε εξ ορισμού +$$\text{supertree\_root\_opt}[u] = 0$$, διαφορετικά, $$ \begin{align*} @@ -163,7 +163,7 @@ $$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ τ μένει είναι να συμπεριλάβουμε το κομμάτι της διαδρομής που εξερευνά τα σπίτια που συνδέονται στο $$u$$ και στον γονέα του. Η τιμή για αυτό το κομμάτι της διαδρομής είναι _σχεδόν_ η τιμή $$\text{subtree\_loop\_opt}[\text{parent}(u)]$$ -που είχαμε ορίσει προηγουμένως, με τη διαφορά ότι αυτή τιμή πιθανώς να +που είχαμε ορίσει προηγουμένως, με τη διαφορά ότι αυτή η τιμή πιθανώς να περιλαμβάνει το κόστος της ακμής $$(u, \text{parent}[u])$$ _δύο φορές_. Πρέπει λοιπόν να διορθώσουμε την τιμή, αφαιρώντας την κατάλληλη ποσότητα, μόνο όμως όταν αυτή έχει θετικό πρόσημο. Τέλος, σε κάθε περίπτωση, συμπεριλαμβάνουμε και @@ -172,17 +172,17 @@ $$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ τ Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διαπεράσεις_ του δέντρου (μια για τον υπογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα μια για τον υπολογισμό των $$\text{supertree\_root\_opt}$$), επομένως -$$\mathcal{O}(N)$$ χρόνος. Συνολικά $$\mathcal{O}(N Q)$$ πολυπλοκότητα και γι +$$\mathcal{O}(N)$$ χρόνος. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα και γι αυτό το υποπρόβλημα. ## Υποπρόβλημα 4 ($$L = R$$) --- Λύση $$\mathcal{O}(N + Q)$$ Σε αυτό το υποπρόβλημα το μέγεθος του δέντρου και το πλήθος των ερωτημάτων -είναι αρκετά μεγαλύτερο και έτσι η λύση που περιγράψαμε νωρίτερα για το -υποπρόβλημα 2 δεν επαρκεί. Θα πρέπει να βρούμε κάποιο τρόπο να απαντάμε τα -ερωτήματα χωρίς να διαπερνάμε όλο το δέντρο από την αρχή κάθε φορά. Ο λόγος που -χρειαζόταν να διαπεράσουμε το δέντρο από την αρχή σε κάθε ερώτημα, ήταν ότι -έπρεπε να αλλάξουμε ρίζα, κι έτσι οι τιμές που είχαμε για το +είναι αρκετά μεγαλύτερο από το προηγούμενο και έτσι η λύση που περιγράψαμε +νωρίτερα για το υποπρόβλημα 2 δεν επαρκεί. Θα πρέπει να βρούμε κάποιον τρόπο να +απαντάμε τα ερωτήματα χωρίς να διαπερνάμε όλο το δέντρο από την αρχή κάθε φορά. +Ο λόγος που χρειαζόταν να διαπεράσουμε το δέντρο από την αρχή σε κάθε ερώτημα, +ήταν ότι έπρεπε να αλλάξουμε ρίζα, κι έτσι οι τιμές που είχαμε για το $$\text{subtree\_loop\_opt}$$ δεν θα ήταν έγκυρες για το δέντρο του επόμενου ερωτήματος. Παρ' όλα αυτά, ας προσπαθήσουμε να σκεφτούμε αν μπορούμε με κάποιο τρόπο να τις εκμεταλλευτούμε ώστε να απαντήσουμε μελλοντικά ερωτήματα. From 4c9d044477db97b7ef341b9354f294160cb363ce Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Thu, 20 Mar 2025 14:17:47 -0700 Subject: [PATCH 04/16] Fix int types in optimal solution --- .../code/37-PDP/tiphunting/optimal.cc | 119 +++++++++--------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc index 247a16aa..b246db5b 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -5,19 +5,19 @@ // #define DEBUG #ifdef DEBUG -#define debug(...) fprintf(stderr, __VA_ARGS__) +#define debug(...) fprintf(stderr, __VA_ARGS__) #else #define debug(...) #endif using namespace std; -using ii = pair; -using iii = pair>; -using vvii = vector>; -using vvi = vector>; +using ll = pair; +using lll = tuple; +using vvll = vector>; +using vvl = vector>; -long positive_part(long x) { return max(0L, x); } +long long positive_part(long long x) { return max(0LL, x); } // Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` // κι όλους τους απογόνους της. @@ -26,7 +26,7 @@ long positive_part(long x) { return max(0L, x); } // και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { +void compute_subtree_loop_opt(vector &subtree_loop_opt, const vvll &tree, const vector &tip, long u, long parent) { subtree_loop_opt[u] = tip[u]; for (auto [v, w]: tree[u]) { @@ -44,7 +44,7 @@ void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, // από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { +void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { assert(0 <= u && u < tree.size()); assert(-1 <= parent && parent < (long)tree.size()); @@ -65,7 +65,7 @@ void compute_supertree_root_opt(vector& supertree_root_opt, const vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { +void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvll& tree, int u, int parent, int w) { assert(0 <= u && u < tree.size()); assert(-1 <= parent && parent < (long)tree.size()); @@ -79,8 +79,8 @@ void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& depth, vector& parent, vector& parent_weight) { - const int n = tree.size(); +void preprocess(const vvll& tree, int u, vector& depth, vector& parent, vector& parent_weight) { + const long n = tree.size(); assert(depth.size() >= n); assert(parent.size() >= n); assert(parent_weight.size() >= n); @@ -100,42 +100,42 @@ void preprocess(const vvii& tree, int u, vector& depth, vector& parent // Set `pred[h][u] = v` when `v` is the `2^h`-th predecessor of `u` // according to the `parent` relation. -void compute_pred(const vector& parent, vvi& pred) { - const int H = pred.size() - 1; - const int n = parent.size(); +void compute_pred(const vector &parent, vvl &pred) +{ + const long H = pred.size() - 1; + const long n = parent.size(); - for (int u = 0; u < n; ++u) + for (long u = 0; u < n; ++u) pred[0][u] = parent[u]; - for (int h = 1; h <= H; ++h) - for (int u = 0; u < n; ++u) { - assert(0 <= pred[h-1][u] && pred[h-1][u] < n); - pred[h][u] = pred[h-1][ pred[h-1][u] ]; + for (long h = 1; h <= H; ++h) + for (long u = 0; u < n; ++u) + { + assert(0 <= pred[h - 1][u] && pred[h - 1][u] < n); + pred[h][u] = pred[h - 1][pred[h - 1][u]]; } } - -iii lca(const vector>& pred, const vector& depth, int u, int v) { - const int H = pred.size() - 1; +lll lca(const vector> &pred, const vector &depth, long u, long v) { + const long H = pred.size() - 1; if (u == v) - return {u, {-1, -1}}; + return {u, -1, -1}; if (depth[u] < depth[v]) { - auto [w, ij] = lca(pred, depth, v, u); - auto [i, j] = ij; - return {w, {j, i}}; + auto [w, i, j] = lca(pred, depth, v, u); + return {w, j, i}; } if (depth[u] != depth[v]) { - for (int h = H; h >= 0; h--) + for (long h = H; h >= 0; h--) if (depth[ pred[h][u] ] > depth[v]) u = pred[h][u]; assert(depth[pred[0][u]] == depth[v]); if (pred[0][u] == v) - return { v, { u, -1} }; + return { v, u, -1 }; u =pred[0][u]; } @@ -143,7 +143,7 @@ iii lca(const vector>& pred, const vector& depth, int u, int v) assert(depth[u] == depth[v]); assert(u != v); - for (int h = H; h >= 0; --h) { + for (long h = H; h >= 0; --h) { if (pred[h][u] != pred[h][v]) { u = pred[h][u]; v = pred[h][v]; @@ -151,27 +151,27 @@ iii lca(const vector>& pred, const vector& depth, int u, int v) } assert(pred[0][u] == pred[0][v]); - return { pred[0][u], { u, v } }; + return { pred[0][u], u, v }; } int main() { int subtask; scanf("%i", &subtask); - int n, q; - scanf("%i%i", &n, &q); + long n, q; + scanf("%li%li", &n, &q); - vector tip(n); - for (int i = 0; i < n; ++i) - scanf("%i", &tip[i]); + vector tip(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); // Αναπαράσταση του δέντρου με adjacency list: // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που // συνδέεται με τη `u` με κόστός `w`. - vvii tree(n, vector{}); - for (int i = 0; i < n-1; ++i) { - int u, v, w; - scanf("%i%i%i", &u, &v, &w); + vvll tree(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); assert(1 <= u && u <= n); assert(1 <= v && v <= n); @@ -182,24 +182,24 @@ int main() { debug("Read the tree\n"); // "Hang" the tree from node 0 and compute the depth of each node. - vector depth(n, 0), parent(n, 0), parent_weight(n, 0); + vector depth(n, 0), parent(n, 0), parent_weight(n, 0); debug("Created vectors\n"); preprocess(tree, 0, depth, parent, parent_weight); debug("preprocessed\n"); - int max_depth = 0; - for (int i = 0; i < n; ++i) + long max_depth = 0; + for (long i = 0; i < n; ++i) max_depth = max(max_depth, depth[i]); // Using the parent relation, compute `pred[h][u]`, the `2^h`-th predecessor // of each node `u` for every `h \in {0, 1, ..., ceil(log_2(n-1))}`. // const int H = int(ceil(log2(n-1))); - const int H = int(ceil(log2(max_depth))); - vector> pred(H+1, vector(n, 0)); + const long H = long(ceil(log2(max_depth))); + vector> pred(H+1, vector(n, 0)); compute_pred(parent, pred); debug("computed pred\n"); - vector subtree_loop_opt(n), supertree_root_opt(n), supertree_loop_opt(n); + vector subtree_loop_opt(n), supertree_root_opt(n), supertree_loop_opt(n); compute_subtree_loop_opt(subtree_loop_opt, tree, tip, 0, -1); debug("done with pass1\n"); compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, 0, -1, -1); @@ -207,23 +207,22 @@ int main() { compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, 0, -1, -1); debug("done with pass3\n"); - for (int i = 0; i < q; ++i) { - int src, dst; - scanf("%i%i", &src, &dst); + for (long i = 0; i < q; ++i) { + long src, dst; + scanf("%li%li", &src, &dst); src -= 1; dst -= 1; if (src == dst) { - printf("%li\n", subtree_loop_opt[src] + supertree_loop_opt[src]); + printf("%lli\n", subtree_loop_opt[src] + supertree_loop_opt[src]); continue; } - auto [mid, uv] = lca(pred, depth, src, dst); - auto [u, v] = uv; + auto [mid, u, v] = lca(pred, depth, src, dst); assert(u != -1 || v != -1); - debug("LCA(%i, %i) = (%i, (%i, %i))\n", src+1, dst+1, mid+1, u+1, v+1); + debug("LCA(%li, %li) = (%li, %li, %li)\n", src+1, dst+1, mid+1, u+1, v+1); - long sol = 0; + long long sol = 0; if (u == -1) { // src is an ancestor of dst. assert(mid == src); @@ -238,25 +237,25 @@ int main() { assert(pred[0][v] == mid); sol = supertree_root_opt[src] + subtree_loop_opt[src] - supertree_root_opt[u]; - debug("%li, ", sol); + debug("%lli, ", sol); sol += supertree_root_opt[dst] + subtree_loop_opt[dst] - supertree_root_opt[v]; - debug("%li, ", sol); + debug("%lli, ", sol); sol -= (parent_weight[u] + parent_weight[v]); debug("%li, ", sol); sol += subtree_loop_opt[mid]; - debug("%li, ", sol); + debug("%lli, ", sol); sol -= positive_part(subtree_loop_opt[u] - 2*parent_weight[u]); - debug("%li, ", sol); + debug("%lli, ", sol); sol -= positive_part(subtree_loop_opt[v] - 2*parent_weight[v]); - debug("%li, ", sol); + debug("%lli, ", sol); sol += supertree_loop_opt[mid]; - debug("%li, ", sol); + debug("%lli, ", sol); debug("\n"); } - printf("%li\n", sol); + printf("%lli\n", sol); } return 0; From 7f31befba33e677e5c3ce95f5f2f0f3da36b2a72 Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Thu, 20 Mar 2025 14:31:52 -0700 Subject: [PATCH 05/16] Fix int types in all subtasks --- .../code/37-PDP/tiphunting/subtask1.cc | 26 +++++------ .../code/37-PDP/tiphunting/subtask2.cc | 38 ++++++++-------- .../code/37-PDP/tiphunting/subtask3.cc | 41 ++++++++--------- .../code/37-PDP/tiphunting/subtask4.cc | 42 +++++++++--------- .../code/37-PDP/tiphunting/subtask5.cc | 44 +++++++++---------- 5 files changed, 96 insertions(+), 95 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc index ad2117db..5994effb 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc @@ -8,25 +8,25 @@ int main() { scanf("%i", &subtask); assert(subtask == 1); - int n, q; - scanf("%i%i", &n, &q); + long n, q; + scanf("%li%li", &n, &q); - long sol = 0; - for (int i = 0; i < n; ++i) { - int t; - scanf("%i", &t); + long long sol = 0; + for (long i = 0; i < n; ++i) { + long t; + scanf("%li", &t); sol += t; } - for (int i = 0; i < n-1; ++i) { - int tmp1, tmp2, tmp3; - scanf("%i%i%i", &tmp1, &tmp2, &tmp3); + for (long i = 0; i < n-1; ++i) { + long tmp1, tmp2, tmp3; + scanf("%li%li%li", &tmp1, &tmp2, &tmp3); } - for (int i = 0; i < q; ++i) { - int src, dst; - scanf("%i%i", &src, &dst); - printf("%li\n", sol); + for (long i = 0; i < q; ++i) { + long src, dst; + scanf("%li%li", &src, &dst); + 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 index 706b31c3..58c18332 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc @@ -4,21 +4,21 @@ using namespace std; -using ii = pair; -using vvii = vector>; +using ll = pair; +using vvll = vector>; -long positive_part(long x) { return max(0L, x); } +long long positive_part(long long x) { return max(0LL, x); } // Επιστρέφει το κέρδος της βέλτιστης διαδρομή η οποία ξεκινάει // από την κορυφή `u`, καταλλήγει πίσω σε αυτή και παραμένει // εξ' ολοκλήρου μέσα στο υποδέντρο που ορίζει η `u` -- με άλλα λόγια, // η διαδρομή απαγορεύεται να διασχίσει το δρόμο `(u, parent)`. -long subtree_loop_opt(const vvii& tree, const vector& tip, int u, int parent) { - long sol = tip[u]; +long long subtree_loop_opt(const vvll& tree, const vector& tip, long u, long parent) { + long long sol = tip[u]; for (auto [v, w]: tree[u]) { if (v == parent) continue; - long s = subtree_loop_opt(tree, tip, v, u); + long long s = subtree_loop_opt(tree, tip, v, u); sol += positive_part(s - 2*w); } @@ -30,22 +30,22 @@ int main() { scanf("%i", &subtask); assert(subtask == 2); - int n, q; - scanf("%i%i", &n, &q); + long n, q; + scanf("%li%li", &n, &q); assert(1 <= n && n <= 1'000); assert(1 <= q && q <= 1'000); - vector tip(n); - for (int i = 0; i < n; ++i) - scanf("%i", &tip[i]); + vector tip(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); // Αναπαράσταση του δέντρου με adjacency list: // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που // συνδέεται με τη `u` με κόστός `w`. - vvii tree(n, vector{}); - for (int i = 0; i < n-1; ++i) { - int u, v, w; - scanf("%i%i%i", &u, &v, &w); + vvll tree(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); assert(1 <= u && u <= n); assert(1 <= v && v <= n); @@ -53,12 +53,12 @@ int main() { tree[v-1].push_back({u-1, w}); } - for (int i = 0; i < q; ++i) { - int src, dst; - scanf("%i%i", &src, &dst); + for (long i = 0; i < q; ++i) { + long src, dst; + scanf("%li%li", &src, &dst); assert(src == dst); - printf("%li\n", subtree_loop_opt(tree, tip, src-1, -1)); + printf("%lli\n", subtree_loop_opt(tree, tip, src-1, -1)); } return 0; diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc index 998f8627..2fbeaa37 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc @@ -4,10 +4,10 @@ using namespace std; -using ii = pair; -using vvii = vector>; +using ll = pair; +using vvll = vector>; -long positive_part(long x) { return max(0L, x); } +long long positive_part(long long x) { return max(0LL, x); } // Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` // κι όλους τους απογόνους της. @@ -16,9 +16,10 @@ long positive_part(long x) { return max(0L, x); } // και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { +void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& tree, const vector& tip, long u, long parent) { subtree_loop_opt[u] = tip[u]; + int x; for (auto [v, w]: tree[u]) { if (v == parent) continue; compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); @@ -34,9 +35,9 @@ void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, // από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { +void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long)tree.size()); + assert(-1 <= parent && parent < (long long)tree.size()); supertree_root_opt[u] = 0; @@ -53,20 +54,20 @@ int main() { scanf("%i", &subtask); assert(subtask == 2 || subtask == 3); - int n, q; - scanf("%i%i", &n, &q); + long n, q; + scanf("%li%li", &n, &q); - vector tip(n); - for (int i = 0; i < n; ++i) - scanf("%i", &tip[i]); + vector tip(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); // Αναπαράσταση του δέντρου με adjacency list: // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που // συνδέεται με τη `u` με κόστός `w`. - vvii tree(n, vector{}); - for (int i = 0; i < n-1; ++i) { - int u, v, w; - scanf("%i%i%i", &u, &v, &w); + vvll tree(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); assert(1 <= u && u <= n); assert(1 <= v && v <= n); @@ -74,17 +75,17 @@ int main() { tree[v-1].push_back({u-1, w}); } - for (int i = 0; i < q; ++i) { - int src, dst; - scanf("%i%i", &src, &dst); + for (long i = 0; i < q; ++i) { + long src, dst; + scanf("%li%li", &src, &dst); src -= 1; dst -= 1; - vector subtree_loop_opt(n), supertree_root_opt(n); + vector subtree_loop_opt(n), supertree_root_opt(n); compute_subtree_loop_opt(subtree_loop_opt, tree, tip, dst, -1); compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, dst, -1, -1); - printf("%li\n", subtree_loop_opt[src] + supertree_root_opt[src]); + printf("%lli\n", subtree_loop_opt[src] + supertree_root_opt[src]); } return 0; diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc index 01d02f86..ce894e1d 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc @@ -4,10 +4,10 @@ using namespace std; -using ii = pair; -using vvii = vector>; +using ll = pair; +using vvll = vector>; -long positive_part(long x) { return max(0L, x); } +long long positive_part(long long x) { return max(0LL, x); } // Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` // κι όλους τους απογόνους της. @@ -16,7 +16,7 @@ long positive_part(long x) { return max(0L, x); } // και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { +void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& tree, const vector& tip, long u, long parent) { subtree_loop_opt[u] = tip[u]; for (auto [v, w]: tree[u]) { @@ -33,9 +33,9 @@ void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, // supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά // ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που // ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. -void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { +void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long)tree.size()); + assert(-1 <= parent && parent < (long long)tree.size()); supertree_loop_opt[u] = 0; @@ -52,20 +52,20 @@ int main() { scanf("%i", &subtask); assert(subtask == 2 || subtask == 4); - int n, q; - scanf("%i%i", &n, &q); + long n, q; + scanf("%li%li", &n, &q); - vector tip(n); - for (int i = 0; i < n; ++i) - scanf("%i", &tip[i]); + vector tip(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); // Αναπαράσταση του δέντρου με adjacency list: // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που // συνδέεται με τη `u` με κόστός `w`. - vvii tree(n, vector{}); - for (int i = 0; i < n-1; ++i) { - int u, v, w; - scanf("%i%i%i", &u, &v, &w); + vvll tree(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); assert(1 <= u && u <= n); assert(1 <= v && v <= n); @@ -73,17 +73,17 @@ int main() { tree[v-1].push_back({u-1, w}); } - vector subtree_loop_opt(n); - vector supertree_loop_opt(n); + vector subtree_loop_opt(n); + vector supertree_loop_opt(n); compute_subtree_loop_opt(subtree_loop_opt, tree, tip, 0, -1); compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, 0, -1, -1); - for (int i = 0; i < q; ++i) { - int src, dst; - scanf("%i%i", &src, &dst); + for (long i = 0; i < q; ++i) { + long src, dst; + scanf("%li%li", &src, &dst); assert(src == dst); - printf("%li\n", subtree_loop_opt[src-1] + supertree_loop_opt[src-1]); + printf("%lli\n", subtree_loop_opt[src-1] + supertree_loop_opt[src-1]); } return 0; diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc index 69539654..631417a1 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc @@ -4,10 +4,10 @@ using namespace std; -using ii = pair; -using vvii = vector>; +using ll = pair; +using vvll = vector>; -long positive_part(long x) { return max(0L, x); } +long long positive_part(long long x) { return max(0LL, x); } // Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` // κι όλους τους απογόνους της. @@ -16,7 +16,7 @@ long positive_part(long x) { return max(0L, x); } // και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, const vector& tip, int u, int parent) { +void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& tree, const vector& tip, long u, long parent) { subtree_loop_opt[u] = tip[u]; for (auto [v, w]: tree[u]) { @@ -34,9 +34,9 @@ void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvii& tree, // από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvii& tree, int u, int parent, int w) { +void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long)tree.size()); + assert(-1 <= parent && parent < (long long)tree.size()); supertree_root_opt[u] = 0; @@ -53,20 +53,20 @@ int main() { scanf("%i", &subtask); assert(subtask == 5); - int n, q; - scanf("%i%i", &n, &q); + long n, q; + scanf("%li%li", &n, &q); - vector tip(n); - for (int i = 0; i < n; ++i) - scanf("%i", &tip[i]); + vector tip(n); + for (long i = 0; i < n; ++i) + scanf("%li", &tip[i]); // Αναπαράσταση του δέντρου με adjacency list: // To `tree[u]` περιέχει ένα vector με pairs `(v, w)` για κάθε κορυφή `v` που // συνδέεται με τη `u` με κόστός `w`. - vvii tree(n, vector{}); - for (int i = 0; i < n-1; ++i) { - int u, v, w; - scanf("%i%i%i", &u, &v, &w); + vvll tree(n); + for (long i = 0; i < n-1; ++i) { + long u, v, w; + scanf("%li%li%li", &u, &v, &w); assert(1 <= u && u <= n); assert(1 <= v && v <= n); @@ -74,24 +74,24 @@ int main() { tree[v-1].push_back({u-1, w}); } - int src, dst; - scanf("%i%i", &src, &dst); + long src, dst; + scanf("%li%li", &src, &dst); src -= 1; dst -= 1; - vector subtree_loop_opt(n), supertree_root_opt(n); + vector subtree_loop_opt(n), supertree_root_opt(n); compute_subtree_loop_opt(subtree_loop_opt, tree, tip, src, -1); compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, src, -1, -1); // Απάντηση στο πρώτο ερώτημα. - printf("%li\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); + printf("%lli\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); // Απάντηση στα υπόλοιπα `q-1` ερωτήματα. - for (int i = 1; i < q; ++i) { - scanf("%i%i", &src, &dst); + for (long i = 1; i < q; ++i) { + scanf("%li%li", &src, &dst); src -= 1; dst -= 1; - printf("%li\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); + printf("%lli\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); } return 0; From d9a5e9822117e9c4ffad8a5d656c6ff665082f96 Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Thu, 20 Mar 2025 15:24:06 -0700 Subject: [PATCH 06/16] Respond to PR comments --- _data/contests/37-PDP.yml | 2 +- .../code/37-PDP/tiphunting/optimal.cc | 2 +- contests/_37-PDP/b-tiphunting-solution.md | 44 ++++++++++++------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/_data/contests/37-PDP.yml b/_data/contests/37-PDP.yml index 42953753..339fdc28 100644 --- a/_data/contests/37-PDP.yml +++ b/_data/contests/37-PDP.yml @@ -91,5 +91,5 @@ tiphunting: solution: true solution_author: "Μάκης Αρσένης" codes_in_git: true - solution_tags: [] + solution_tags: [lca, dfs, tree, binary lifting] on_judge: false diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc index b246db5b..b29ec580 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -7,7 +7,7 @@ #ifdef DEBUG #define debug(...) fprintf(stderr, __VA_ARGS__) #else -#define debug(...) +#define debug(...) ((void)0) #endif using namespace std; diff --git a/contests/_37-PDP/b-tiphunting-solution.md b/contests/_37-PDP/b-tiphunting-solution.md index 3c833233..542f05d9 100644 --- a/contests/_37-PDP/b-tiphunting-solution.md +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -26,7 +26,7 @@ Change log: Κάθε σπίτι $$i$$ μπορεί να προσφέρει ένα μη-αρνητικό φιλοδώρημα $$t_i$$, και κάθε δρόμος $$j$$ που συνδέει δύο σπίτια $$u, v$$ έχει ένα μη-αρνητικό κόστος -διαπέρασης που θα συμβολίζουμε με $$w_j$$ ή ισοδύναμα με $$w_{u, v}$$. Ως +διάσχισης που θα συμβολίζουμε με $$w_j$$ ή ισοδύναμα με $$w_{u, v}$$. Ως _διαδρομή_ ορίζουμε μια πεπερασμένη ακολουθία σπιτιών όπου διαδοχικά σπίτια συνδέονται μεταξύ τους άμεσα από κάποιον δρόμο. Παρατηρήστε ότι ο παραπάνω ορισμός επιτρέπει σε μια διαδρομή να περιλαμβάνει το ίδιο σπίτι _περισσότερες @@ -68,6 +68,10 @@ $$u$$ (άμεσα ή έμμεσα μέσω άλλων δρόμων), να μαζ βρίσκονται στο κεντρικό μονοπάτι μεταξύ $$L$$ και $$R$$ μόνο μια φορά, και για κάθε άλλο δρόμο θα τον έχουμε διασχίσει είτε καμία είτε δύο φορές. +Τέλος, παρατηρούμε οτι η απάντηση στο ερώτημα $$L, R$$ είναι η ίδια με +την απάντηση στο ερώτημα $$R, L$$ λόγω συμμετρίας (οι δρόμοι είναι διπλής +κατεύθυνσης). + ## Υποπρόβλημα 2 ($$N \le 1.000, Q \le 1.000, L = R$$) --- Λύση $$\mathcal{O}(N Q)$$ Ο περιορισμός $$L = R$$ απλουστεύει το πρόβλημα καθώς δεν χρειάζεται να @@ -103,8 +107,8 @@ $$u$$. $$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} \left( \text{subtree\_loop\_opt}[v] - 2 \cdot w_{u, v} \right)^+ $$ -όπου ο συμβολισμός $$(x)^+$$ σημαίνει $$\max(0, x)$$. - +όπου ο συμβολισμός $$(x)^+$$ σημαίνει $$\max(0, x)$$ και $$\text{children}[u]$$ +είναι το σύνολο με όλα τα παιδιά της κορυφής $$u$$. Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{subree\_loop\_opt}[L]$$. @@ -169,11 +173,11 @@ $$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ τ όταν αυτή έχει θετικό πρόσημο. Τέλος, σε κάθε περίπτωση, συμπεριλαμβάνουμε και το κόστος διάσχισης του δρόμου $$(u, \text{parent}[u])$$ ακριβώς μία φορά. -Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διαπεράσεις_ του -δέντρου (μια για τον υπογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα μια -για τον υπολογισμό των $$\text{supertree\_root\_opt}$$), επομένως -$$\mathcal{O}(N)$$ χρόνος. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα και γι -αυτό το υποπρόβλημα. +Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διασχίσεις_ του +δέντρου (μια για τον υπολογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα +μια για τον υπολογισμό των $$\text{supertree\_root\_opt}$$), επομένως +$$\mathcal{O}(N)$$ χρόνος. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα και +γι' αυτό το υποπρόβλημα. ## Υποπρόβλημα 4 ($$L = R$$) --- Λύση $$\mathcal{O}(N + Q)$$ @@ -189,8 +193,8 @@ $$\text{subtree\_loop\_opt}$$ δεν θα ήταν έγκυρες για το δ Ας θεωρήσουμε λοιπόν στο εξής ότι η ρίζα του δέντρου είναι πάντα η κορυφή 1, κι ας υπολογίσουμε τις τιμές $$\text{subtree\_loop\_opt}$$ όπως τις ορίσαμε -παραπάνω. Η απάντηση ένα ερώτημα στο οποίο $$L = R \ne 1$$, είναι _σχεδόν_ ίση -με $$\text{subtree\_loop\_opt}[L]$$, με τη διαφορά ότι αυτή η τιμή δεν +παραπάνω. Η απάντηση σε ένα ερώτημα στο οποίο $$L = R \ne 1$$, είναι _σχεδόν_ +ίση με $$\text{subtree\_loop\_opt}[L]$$, με τη διαφορά ότι αυτή η τιμή δεν περιλαμβάνει διαδρομές που ανεβαίνουν σε υψηλότερα επίπεδα του δέντρου και γυρνάνε πάλι πίσω στην κορυφή $$L$$. Αυτό όμως διορθώνεται εύκολα αν προ-υπολογίσουμε για κάθε κορυφή $$u$$ αυτή την τιμή, ας την ονομάσουμε @@ -255,8 +259,7 @@ $$\text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]$$. όπως στον ορισμό του $$\text{supertree\_loop\_opt}$$ παραπάνω). Για την ώρα, ας υποθέσουμε ότι γνωρίζουμε τον ελάχιστο κοινό πρόγονο $$z = -LCA(L, R)$$ --- θα δούμε σύντομα πώς να τον υπολογίσουμε αποδοτικά με κατάλληλη -προεργασία. +LCA(L, R)$$. Έχουμε τις παρακάτω περιπτώσεις: @@ -291,8 +294,9 @@ LCA(L, R)$$ --- θα δούμε σύντομα πώς να τον υπολογί Για να διορθώσουμε την απάντηση, θα φανεί χρήσιμο να γνωρίσουμε τις κορυφές $$u, v$$ οι οποίες είναι τα παιδιά της $$z$$ που οδηγούν προς τις κορυφές - $$L$$ και $$R$$ αντίστοιχα. Μπορούμε να τις υπολογίσουμε μαζί με την $$z$$ -- - θα δούμε σύντομα πώς. Έχοντας τις $$u, v$$, υπολογίζουμε την απάντηση του + $$L$$ και $$R$$ αντίστοιχα. Ας υποθέσουμε ότι τις γνωρίζουμε -- + μπορούμε να τις βρούμε κατα τον υπολογισμό της $$z$$ όπως αναφέρουμε + παρακάτω. Έχοντας τις $$u, v$$, υπολογίζουμε την απάντηση του ερωτήματος συνδυάζοντας τις παρακάτω τιμές: - (a) το κέρδος της βέλτιστης διαδρομή από την κορυφή $$L$$ μέχρι την κορυφή $$u$$, έπειτα @@ -329,4 +333,14 @@ LCA(L, R)$$ --- θα δούμε σύντομα πώς να τον υπολογί χρόνο), αρκεί να μπορούμε να υπολογίσουμε τον ελάχιστο κοινό πρόγονο μαζί με τις κορυφές $$u, v$$ όπως τις ορίσαμε παραπάνω. -**TODO**: LCA +Για να το κάνουμε αυτό αποδοτικά, μπορούμε να χρησιμοποιήσουμε μια τεχνική που +λέγεται **Binary Lifting** και η οποία μας επιτρέπει να υπολογίσουμε +τον ελάχιστο κοινό πρόγονο δύο κορυφών σε χρόνο $$\mathcal{O}(\log N)$$, έχοντας +κάνει κατάλληλη προεργασία σε χρόνο $$\mathcal{O}(N \log N)$$ μια φορά στην +αρχή. Προτείνουμε να ανατρέξετε +[εδώ](https://cp-algorithms.com/graph/lca_binary_lifting.html) για μια +αναλυτική περιγραφή, ή δείτε τα σχόλια στην παρακάτω υλοποίηση. + +Η λύση που περιγράψαμε έχει συνολική πολυπλοκότητα $$\mathcal{O} +( (N + Q) \log N )$$ η οποία μας καλύπτει για τους περιορισμούς +του προβλήματος. \ No newline at end of file From 318f3fcbffcb42c64dd0992c8331575f40a2b1ce Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Thu, 20 Mar 2025 19:02:52 -0700 Subject: [PATCH 07/16] Improve comments in optimal solution --- .../code/37-PDP/tiphunting/optimal.cc | 177 ++++++++++-------- 1 file changed, 99 insertions(+), 78 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc index b29ec580..1872c64f 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -19,12 +19,42 @@ using vvl = vector>; long long positive_part(long long x) { return max(0LL, x); } -// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` -// κι όλους τους απογόνους της. +// Διασχίζει το δέντρο `tree` ξεκινώντας από την κορυφή `u` και υπολογίζει +// αναδρομικά τις τιμές `depth[v]`, `parent[v]` και `parent_weight[v]` για κάθε +// κορυφή `v != u` στο υποδέντρο της `u`. Οι τιμές `depth[u]`, `parent[u]` και +// `parent_weight[u]` θα πρέπει να έχουν ήδη υπολογισθεί από τον caller. // -// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// `depth[u]`: Το βάθος του `u` στο δέντρο, το οποίο ορίζεται ως το πλήθος +// των ακμών στο μονοπάτι από τον `u` προς τη ρίζα. Για παράδειγμα το βάθος +// της ρίζας είναι 0. +// `parent[u]`: Ο γονέας του `u`. +// `parent_weight[u]`: Κόστος του δρόμου που συνδέει τον `u` με τον γονέα του. +void compute_auxiliary(const vvll& tree, int u, vector& depth, vector& parent, vector& parent_weight) { + const long n = tree.size(); + assert(depth.size() >= n); + assert(parent.size() >= n); + assert(parent_weight.size() >= n); + assert(0 <= u && u < n); + + for (auto [v, w]: tree[u]) { + if (v == parent[u]) continue; + assert(0 <= v && v < n); + + parent[v] = u; + parent_weight[v] = w; + depth[v] = depth[u] + 1; + + compute_auxiliary(tree, v, depth, parent, parent_weight); + } +} + +// Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές +// `subtree_loop_opt` για την κορυφή `u` κι όλους τους απογόνους της. Η τιμή της +// `parent` είναι ο γονέας της `u`. +// +// `subtree_loop_opt[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// η κορυφή `u`. Mε άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. void compute_subtree_loop_opt(vector &subtree_loop_opt, const vvll &tree, const vector &tip, long u, long parent) { subtree_loop_opt[u] = tip[u]; @@ -36,12 +66,13 @@ void compute_subtree_loop_opt(vector &subtree_loop_opt, const vvll &t } } -// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή -// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές +// `subtree_root_opt` για την κορυφή `u` κι όλους τους απογόνους της, +// χρησιμοποιώντας τις τιμές `subtree_loop_opt` που υπολογίσαμε ήδη στην +// προηγούμενη διάσχιση. Η τιμή της `parent` είναι ο γονέας της `u`. // -// supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και +// `supertree_root_opt[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει +// από την κορυφή `u`, καταλήγει στη ρίζα του δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { @@ -58,9 +89,10 @@ void compute_supertree_root_opt(vector& supertree_root_opt, const vec compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, v, u, w); } -// Τρίτη διαπέραση η οποία υπολογίζει το `supertree_loop_opt` για την κορυφή -// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές +// `subtree_loop_opt` για την κορυφή `u` κι όλους τους απογόνους της, +// χρησιμοποιώντας τις τιμές `subtree_loop_opt` που υπολογίσαμε ήδη στην +// προηγούμενη διάσχιση. Η τιμή της `parent` είναι ο γονέας της `u`. // // supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά // ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που @@ -79,27 +111,13 @@ void compute_supertree_loop_opt(vector& supertree_loop_opt, const vec compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, v, u, w); } -void preprocess(const vvll& tree, int u, vector& depth, vector& parent, vector& parent_weight) { - const long n = tree.size(); - assert(depth.size() >= n); - assert(parent.size() >= n); - assert(parent_weight.size() >= n); - assert(0 <= u && u < n); - - for (auto [v, w]: tree[u]) { - if (v == parent[u]) continue; - assert(0 <= v && v < n); - - parent[v] = u; - parent_weight[v] = w; - depth[v] = depth[u] + 1; - - preprocess(tree, v, depth, parent, parent_weight); - } -} - -// Set `pred[h][u] = v` when `v` is the `2^h`-th predecessor of `u` -// according to the `parent` relation. +// Υπολογίζει τον πίνακα `pred` έτσι ώστε: +// `pred[h][u] == v` αν και μόνο αν ο `v` είναι ο `2^h`-πρόγονος του `u`. +// Για παράδειγμα `pred[0][u] == parent[u]` γιατί ο γονέας του $u$ +// είναι ο `2^0 = 1`-ος πρόγονός του. +// Ο πίνακας εισόδου `parent` θα πρέπει για κάθε κορυφή να περιέχει +// τον γονέα της, εκτός από την ρίζα `r` για την οποία θα πρέπει να ισχύει +// `parent[r] == r`. void compute_pred(const vector &parent, vvl &pred) { const long H = pred.size() - 1; @@ -116,6 +134,15 @@ void compute_pred(const vector &parent, vvl &pred) } } +// Υπολογίζει τρεις τιμές `(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(const vector> &pred, const vector &depth, long u, long v) { const long H = pred.size() - 1; @@ -179,80 +206,74 @@ int main() { tree[v-1].push_back({u-1, w}); } - debug("Read the tree\n"); - - // "Hang" the tree from node 0 and compute the depth of each node. + // Αρχικοποιώντας `depth[0] = 0`, `parent[0] = 0` θέτουμε την κορυφή + // 0 ως ρίζα του δέντρου. Η συνάρτηση `compute_auxiliary` συμπληρώνει + // τις τιμές και για τους υπόλοιπους κόμβους. vector depth(n, 0), parent(n, 0), parent_weight(n, 0); - debug("Created vectors\n"); - preprocess(tree, 0, depth, parent, parent_weight); - debug("preprocessed\n"); + compute_auxiliary(tree, 0, depth, parent, parent_weight); + // Θα χρειαστούμε το μέγιστο βάθος ώστε να υπολογίσουμε τις διαστάσεις + // πίνακα `pred` παρακάτω. long max_depth = 0; for (long i = 0; i < n; ++i) max_depth = max(max_depth, depth[i]); - // Using the parent relation, compute `pred[h][u]`, the `2^h`-th predecessor - // of each node `u` for every `h \in {0, 1, ..., ceil(log_2(n-1))}`. - // const int H = int(ceil(log2(n-1))); + // Υπολογισμός του πίνακα `pred` από τον πίνακα `parent`. + // Το δέντρο έχει ύψος `max_depth` επομένως θα χρειαστούμε τους + // απογόνους (predecessors) το πολύ μέχρι `max_depth <= 2^H` επίπεδα παραπάνω. const long H = long(ceil(log2(max_depth))); vector> pred(H+1, vector(n, 0)); compute_pred(parent, pred); - debug("computed pred\n"); vector subtree_loop_opt(n), supertree_root_opt(n), supertree_loop_opt(n); compute_subtree_loop_opt(subtree_loop_opt, tree, tip, 0, -1); - debug("done with pass1\n"); compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, 0, -1, -1); - debug("done with pass2\n"); compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, 0, -1, -1); - debug("done with pass3\n"); for (long i = 0; i < q; ++i) { - long src, dst; - scanf("%li%li", &src, &dst); - src -= 1; - dst -= 1; + long L, R; + scanf("%li%li", &L, &R); + L -= 1; + R -= 1; - if (src == dst) { - printf("%lli\n", subtree_loop_opt[src] + supertree_loop_opt[src]); + if (L == R) { + printf("%lli\n", subtree_loop_opt[L] + supertree_loop_opt[L]); continue; } - auto [mid, u, v] = lca(pred, depth, src, dst); + auto [z, u, v] = lca(pred, depth, L, R); assert(u != -1 || v != -1); - debug("LCA(%li, %li) = (%li, %li, %li)\n", src+1, dst+1, mid+1, u+1, v+1); long long sol = 0; if (u == -1) { - // src is an ancestor of dst. - assert(mid == src); - sol = supertree_root_opt[dst] - supertree_root_opt[src] + supertree_loop_opt[src] + subtree_loop_opt[dst]; + // Η κορυφή `L` είναι πρόγονος της `R`. + assert(z == L); + sol = supertree_root_opt[R] - supertree_root_opt[L] + supertree_loop_opt[L] + subtree_loop_opt[R]; } else if (v == -1) { - // dst is an ancestor of src. - assert(mid == dst); - sol = supertree_root_opt[src] - supertree_root_opt[dst] + supertree_loop_opt[dst] + subtree_loop_opt[src]; + // Η κορυφή `R` είναι πρόγονος της `L`. + assert(z == R); + sol = supertree_root_opt[L] - supertree_root_opt[R] + supertree_loop_opt[R] + subtree_loop_opt[L]; } else { - // `src` and `dst` have a common ancestor. - assert(pred[0][u] == mid); - assert(pred[0][v] == mid); - - sol = supertree_root_opt[src] + subtree_loop_opt[src] - supertree_root_opt[u]; - debug("%lli, ", sol); - sol += supertree_root_opt[dst] + subtree_loop_opt[dst] - supertree_root_opt[v]; - debug("%lli, ", sol); - sol -= (parent_weight[u] + parent_weight[v]); - debug("%li, ", sol); + // Οι κορυφές `L, R` έχουν κοινό πρόγονο τον `z != L, R`. + assert(pred[0][u] == z); + assert(pred[0][v] == z); + + // (a) + sol = supertree_root_opt[L] - supertree_root_opt[u] + subtree_loop_opt[L] ; - sol += subtree_loop_opt[mid]; - debug("%lli, ", sol); + // (b) + sol += supertree_root_opt[R] - supertree_root_opt[v] + subtree_loop_opt[R]; + + // (c) + sol += subtree_loop_opt[z]; sol -= positive_part(subtree_loop_opt[u] - 2*parent_weight[u]); - debug("%lli, ", sol); sol -= positive_part(subtree_loop_opt[v] - 2*parent_weight[v]); - debug("%lli, ", sol); - sol += supertree_loop_opt[mid]; - debug("%lli, ", sol); - debug("\n"); + // (d) + sol += supertree_loop_opt[z]; + + // (e) + sol -= (parent_weight[u] + parent_weight[v]); } printf("%lli\n", sol); From b0c7f49f4ee13bba31be22d32bc185c7fa3d1c7c Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Thu, 20 Mar 2025 19:33:33 -0700 Subject: [PATCH 08/16] Utilize global variables --- .../code/37-PDP/tiphunting/optimal.cc | 116 ++++++++---------- 1 file changed, 51 insertions(+), 65 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc index 1872c64f..9a8971b5 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -15,8 +15,14 @@ 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 subtree_loop_opt, supertree_loop_opt, supertree_root_opt; + long long positive_part(long long x) { return max(0LL, x); } // Διασχίζει το δέντρο `tree` ξεκινώντας από την κορυφή `u` και υπολογίζει @@ -29,39 +35,30 @@ long long positive_part(long long x) { return max(0LL, x); } // της ρίζας είναι 0. // `parent[u]`: Ο γονέας του `u`. // `parent_weight[u]`: Κόστος του δρόμου που συνδέει τον `u` με τον γονέα του. -void compute_auxiliary(const vvll& tree, int u, vector& depth, vector& parent, vector& parent_weight) { - const long n = tree.size(); - assert(depth.size() >= n); - assert(parent.size() >= n); - assert(parent_weight.size() >= n); - assert(0 <= u && u < n); - +void compute_auxiliary(int u) { for (auto [v, w]: tree[u]) { if (v == parent[u]) continue; - assert(0 <= v && v < n); - parent[v] = u; parent_weight[v] = w; depth[v] = depth[u] + 1; - compute_auxiliary(tree, v, depth, parent, parent_weight); + compute_auxiliary(v); } } // Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές -// `subtree_loop_opt` για την κορυφή `u` κι όλους τους απογόνους της. Η τιμή της -// `parent` είναι ο γονέας της `u`. +// `subtree_loop_opt` για την κορυφή `u` κι όλους τους απογόνους της. // // `subtree_loop_opt[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u`. Mε άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector &subtree_loop_opt, const vvll &tree, const vector &tip, long u, long parent) { +void compute_subtree_loop_opt(long u) { subtree_loop_opt[u] = tip[u]; for (auto [v, w]: tree[u]) { - if (v == parent) continue; - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + if (v == parent[u]) continue; + compute_subtree_loop_opt(v); subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); } } @@ -69,58 +66,54 @@ void compute_subtree_loop_opt(vector &subtree_loop_opt, const vvll &t // Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές // `subtree_root_opt` για την κορυφή `u` κι όλους τους απογόνους της, // χρησιμοποιώντας τις τιμές `subtree_loop_opt` που υπολογίσαμε ήδη στην -// προηγούμενη διάσχιση. Η τιμή της `parent` είναι ο γονέας της `u`. +// προηγούμενη διάσχιση. // // `supertree_root_opt[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // από την κορυφή `u`, καταλήγει στη ρίζα του δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { - assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long)tree.size()); - +void compute_supertree_root_opt(long u) { supertree_root_opt[u] = 0; - if (parent != -1) - supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; + if (parent[u] != -1) + supertree_root_opt[u] = + subtree_loop_opt[parent[u]] + supertree_root_opt[parent[u]] + - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) - parent_weight[u]; for (auto [v, w]: tree[u]) - if (v != parent) - compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, v, u, w); + if (v != parent[u]) + compute_supertree_root_opt(v); } // Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές // `subtree_loop_opt` για την κορυφή `u` κι όλους τους απογόνους της, // χρησιμοποιώντας τις τιμές `subtree_loop_opt` που υπολογίσαμε ήδη στην -// προηγούμενη διάσχιση. Η τιμή της `parent` είναι ο γονέας της `u`. +// προηγούμενη διάσχιση. // // supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά // ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που // ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. -void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvll& tree, int u, int parent, int w) { - assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long)tree.size()); - +void compute_supertree_loop_opt(int u) { supertree_loop_opt[u] = 0; - if (parent != -1) - supertree_loop_opt[u] = positive_part(subtree_loop_opt[parent] + supertree_loop_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - 2*w); + if (parent[u] != -1) + supertree_loop_opt[u] = + positive_part(subtree_loop_opt[parent[u]] + supertree_loop_opt[parent[u]] + - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) - 2*parent_weight[u]); for (auto [v, w]: tree[u]) - if (v != parent) - compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, v, u, w); + if (v != parent[u]) + compute_supertree_loop_opt(v); } -// Υπολογίζει τον πίνακα `pred` έτσι ώστε: +// Υπολογίζει τον πίνακα `pred` έτσι ώστε για κάθε 0 <= h <= H, 0 <= u < N: // `pred[h][u] == v` αν και μόνο αν ο `v` είναι ο `2^h`-πρόγονος του `u`. // Για παράδειγμα `pred[0][u] == parent[u]` γιατί ο γονέας του $u$ // είναι ο `2^0 = 1`-ος πρόγονός του. -// Ο πίνακας εισόδου `parent` θα πρέπει για κάθε κορυφή να περιέχει -// τον γονέα της, εκτός από την ρίζα `r` για την οποία θα πρέπει να ισχύει -// `parent[r] == r`. -void compute_pred(const vector &parent, vvl &pred) -{ - const long H = pred.size() - 1; +// O caller θα πρέπει να έχει ήδη υπολογίσει τον πίνακα parent +// (δες `compute_auxiliary`) έτσι ώστε η τιμή `parent[u]` να είναι +// ο γονέας της `u`, εκτός από την ρίζα `r` για την οποία `r == parent[r]`. +void compute_pred(long H) { const long n = parent.size(); for (long u = 0; u < n; ++u) @@ -128,10 +121,7 @@ void compute_pred(const vector &parent, vvl &pred) for (long h = 1; h <= H; ++h) for (long u = 0; u < n; ++u) - { - assert(0 <= pred[h - 1][u] && pred[h - 1][u] < n); pred[h][u] = pred[h - 1][pred[h - 1][u]]; - } } // Υπολογίζει τρεις τιμές `(z, a, b)` όπου `z` είναι ο Ελάχιστος Κοινός Πρόγονος @@ -140,17 +130,17 @@ void compute_pred(const vector &parent, vvl &pred) // αντίστοιχα `b` είναι η μοναδική κορυφή στο μονοπάτι από `z` προς `v` τέτοια // ώστε `parent[v] == z` (ή `b == -1` αν τέτοια κορυφή δεν υπάρχει). // -// Η συνάρτηση λαμβάνει ως είσοδο και τον πίνακα `pred` που υπολόγισε νωρίτερα η +// Η συνάρτηση χρησιμοποιεί τον πίνακα `pred` που υπολόγισε νωρίτερα η // συνάρτηση `compute_pred` καθώς και τον πίνακα `depth` που υπολόγισε νωρίτερα // η συνάρτηση `compute_auxiliary`. -lll lca(const vector> &pred, const vector &depth, long u, long v) { +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(pred, depth, v, u); + auto [w, i, j] = lca(v, u); return {w, j, i}; } @@ -158,8 +148,6 @@ lll lca(const vector> &pred, const vector &depth, long u, lon for (long h = H; h >= 0; h--) if (depth[ pred[h][u] ] > depth[v]) u = pred[h][u]; - - assert(depth[pred[0][u]] == depth[v]); if (pred[0][u] == v) return { v, u, -1 }; @@ -167,9 +155,6 @@ lll lca(const vector> &pred, const vector &depth, long u, lon u =pred[0][u]; } - assert(depth[u] == depth[v]); - assert(u != v); - for (long h = H; h >= 0; --h) { if (pred[h][u] != pred[h][v]) { u = pred[h][u]; @@ -177,7 +162,6 @@ lll lca(const vector> &pred, const vector &depth, long u, lon } } - assert(pred[0][u] == pred[0][v]); return { pred[0][u], u, v }; } @@ -188,19 +172,17 @@ int main() { long n, q; scanf("%li%li", &n, &q); - vector tip(n); + 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`. - vvll tree(n); + tree.resize(n); for (long i = 0; i < n-1; ++i) { long u, v, w; scanf("%li%li%li", &u, &v, &w); - assert(1 <= u && u <= n); - assert(1 <= v && v <= n); tree[u-1].push_back({v-1, w}); tree[v-1].push_back({u-1, w}); @@ -209,8 +191,10 @@ int main() { // Αρχικοποιώντας `depth[0] = 0`, `parent[0] = 0` θέτουμε την κορυφή // 0 ως ρίζα του δέντρου. Η συνάρτηση `compute_auxiliary` συμπληρώνει // τις τιμές και για τους υπόλοιπους κόμβους. - vector depth(n, 0), parent(n, 0), parent_weight(n, 0); - compute_auxiliary(tree, 0, depth, parent, parent_weight); + depth.resize(n, 0); + parent.resize(n, 0); + parent_weight.resize(n, 0); + compute_auxiliary(0); // Θα χρειαστούμε το μέγιστο βάθος ώστε να υπολογίσουμε τις διαστάσεις // πίνακα `pred` παρακάτω. @@ -222,13 +206,15 @@ int main() { // Το δέντρο έχει ύψος `max_depth` επομένως θα χρειαστούμε τους // απογόνους (predecessors) το πολύ μέχρι `max_depth <= 2^H` επίπεδα παραπάνω. const long H = long(ceil(log2(max_depth))); - vector> pred(H+1, vector(n, 0)); - compute_pred(parent, pred); + pred.resize(H+1, vector(n, 0)); + compute_pred(H); - vector subtree_loop_opt(n), supertree_root_opt(n), supertree_loop_opt(n); - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, 0, -1); - compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, 0, -1, -1); - compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, 0, -1, -1); + subtree_loop_opt.resize(n); + supertree_loop_opt.resize(n); + supertree_root_opt.resize(n); + compute_subtree_loop_opt(0); + compute_supertree_root_opt(0); + compute_supertree_loop_opt(0); for (long i = 0; i < q; ++i) { long L, R; @@ -241,7 +227,7 @@ int main() { continue; } - auto [z, u, v] = lca(pred, depth, L, R); + auto [z, u, v] = lca(L, R); assert(u != -1 || v != -1); long long sol = 0; From 26e1de9b9457e81ef037184e4377129ed0e6472e Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Fri, 21 Mar 2025 10:56:14 -0700 Subject: [PATCH 09/16] Address PR comments --- contests/_37-PDP/b-tiphunting-solution.md | 71 ++++++++++++----------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/contests/_37-PDP/b-tiphunting-solution.md b/contests/_37-PDP/b-tiphunting-solution.md index 542f05d9..f2fc1b3e 100644 --- a/contests/_37-PDP/b-tiphunting-solution.md +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -54,23 +54,26 @@ _διαδρομή_ ορίζουμε μια πεπερασμένη ακολουθ ## Γενικές Παρατηρήσεις Πριν προχωρήσουμε, ας παρατηρήσουμε κάποιες ιδιότητες της βέλτιστης λύσης που -θα μας φανούν χρήσιμα σε όλα τα υποπροβλήματα που ακολουθούν. Αρχικά, στη -βέλτιστη λύση δεν θα χρειαστεί ποτέ να διασχίσουμε κάποιο δρόμο πάνω από δύο -φορές (μπορείτε να δείτε γιατί;). - -Γενικότερα, η βέλτιστη λύση θα είναι μια διαδρομή η οποία θα περιλαμβάνει -το (μοναδικό) μονοπάτι που συνδέει τα σπίτια $$L$$ και $$R$$, όπου σε κάθε -στάση σε κάποιο σπίτι $$u$$, θα έχουμε την ευκαιρία να "ξεφύγουμε" για λίγο από -το βασικό του μονοπάτι και να εξερευνήσουμε τα σπίτια που συνδέονται με το -$$u$$ (άμεσα ή έμμεσα μέσω άλλων δρόμων), να μαζέψουμε όσα περισσότερα -φιλοδωρήματα μπορούμε, να επιστρέψουμε πάλι στο $$u$$ και να συνεχίσουμε προς -τον τελικό μας προορισμό. Έτσι, στο τέλος θα έχουμε διασχίσει τους δρόμους που -βρίσκονται στο κεντρικό μονοπάτι μεταξύ $$L$$ και $$R$$ μόνο μια φορά, και για -κάθε άλλο δρόμο θα τον έχουμε διασχίσει είτε καμία είτε δύο φορές. - -Τέλος, παρατηρούμε οτι η απάντηση στο ερώτημα $$L, R$$ είναι η ίδια με -την απάντηση στο ερώτημα $$R, L$$ λόγω συμμετρίας (οι δρόμοι είναι διπλής -κατεύθυνσης). +θα μας φανούν χρήσιμα σε όλα τα υποπροβλήματα που ακολουθούν. + +**Παρατήρηση 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 Q)$$ @@ -78,7 +81,7 @@ $$u$$ (άμεσα ή έμμεσα μέσω άλλων δρόμων), να μαζ ασχοληθούμε με το μέρος της λύσης που περιλαμβάνει το μονοπάτι που αναφέραμε προηγουμένως, παρά μόνο με την επιμέρους εξερεύνηση. -Ας φανταστούμε ότι το σπίτι $$L$$ από το οποία ξεκινάμε είναι η ρίζα του +Ας φανταστούμε ότι το σπίτι $$L$$ από το οποίο ξεκινάμε είναι η ρίζα του δέντρου που αναπαριστά το οδικό δίκτυο. Κάθε σπίτι $$u$$ που συνδέεται άμεσα με το $$L$$ ορίζει ένα _υποδέντρο_ το οποίο θα συμβολίζουμε με $$\text{subtree}(u)$$. Για κάθε ένα από αυτά τα δέντρα, έχουμε να κάνουμε μια @@ -90,7 +93,7 @@ $$\text{subtree}(u)$$. Για κάθε ένα από αυτά τα δέντρα 2. να _μη διασχίσουμε ποτέ_ το δρόμο που συνδέει το σπίτι $$L$$ με το σπίτι $$u$$. -Ας συμβολίσουμε με $$\text{subtree\_loop\_opt}[u]$$ το μέγιστό δυνατό κέρδος +Ας ορίσουμε $$\text{subtree\_loop\_opt}[u]$$ ως το μέγιστό δυνατό κέρδος που μπορούμε να έχουμε αν ξεκινήσουμε από το σπίτι $$u$$, κινούμαστε μόνο μέσα στο υποδέντρο που ορίζει το σπίτι $$u$$, και τελικά επιστρέψουμε πάλι στο $$u$$. @@ -107,7 +110,7 @@ $$u$$. $$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} \left( \text{subtree\_loop\_opt}[v] - 2 \cdot w_{u, v} \right)^+ $$ -όπου ο συμβολισμός $$(x)^+$$ σημαίνει $$\max(0, x)$$ και $$\text{children}[u]$$ +όπου ο συμβολισμός $$(x)^+$$ σημαίνει $$\max(0, x)$$ και $$\text{children}(u)$$ είναι το σύνολο με όλα τα παιδιά της κορυφής $$u$$. Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{subree\_loop\_opt}[L]$$. @@ -133,7 +136,7 @@ $$\text{subtree}(L)$$ για το οποίο μπορούμε να υπολογ Σε αντίθεση όμως με πριν, μόλις επιστρέψουμε στο $$L$$ θα πρέπει να ανέβουμε ένα επίπεδο παραπάνω, ακολουθώντας το μονοπάτι προς τη ρίζα και μαζεύοντας κι άλλα φιλοδωρήματα στην πορεία. Ας ορίσουμε λοιπόν την ποσότητα -$$\text{supertree\_root\_opt}[u]$$ η οποία θα είναι ίση με το μέγιστο κέρδος που +$$\text{supertree\_root\_opt}[u]$$ ως το μέγιστο κέρδος που μπορούμε να έχουμε όταν ξεκινάμε από το σπίτι $$u$$ (χωρίς όμως να συλλέγουμε το φιλοδώρημα του $$u$$), καταλλήγουμε τελικά στη ρίζα $$R$$ του δέντρου, και σε όλη τη διαδρομή απαγορεύεται να επισκεφτούμε οποιοδήποτε σπίτι βρίσκεται στο @@ -142,7 +145,7 @@ $$\text{supertree\_root\_opt}[u]$$ η οποία θα είναι ίση με τ Εαν υπολογίσουμε την τιμή της παραπάνω ποσότητας για την κορυφή $$L$$, τότε μπορούμε να υπολογίσουμε την απάντηση του ερωτήματος απλά με το άθροισμα: -$$ \text{subtree\_loop\_opt}[L] + \text{supertree\_root\_opt}[L] $$. +$$ \text{subtree\_loop\_opt}[L] + \text{supertree\_root\_opt}[L]. $$ Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{supertree\_root\_opt}[u]$$ από "πάνω προς τα κάτω" χρησιμοποιώντας τις τιμές $$\text{subtree\_loop\_opt}$$ ως @@ -173,11 +176,11 @@ $$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ τ όταν αυτή έχει θετικό πρόσημο. Τέλος, σε κάθε περίπτωση, συμπεριλαμβάνουμε και το κόστος διάσχισης του δρόμου $$(u, \text{parent}[u])$$ ακριβώς μία φορά. -Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διασχίσεις_ του -δέντρου (μια για τον υπολογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα -μια για τον υπολογισμό των $$\text{supertree\_root\_opt}$$), επομένως -$$\mathcal{O}(N)$$ χρόνος. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα και -γι' αυτό το υποπρόβλημα. +Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διασχίσεις_ του δέντρου +(μια για τον υπολογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα μια για +τον υπολογισμό των $$\text{supertree\_root\_opt}$$), οι οποίες γίνονται σε +$$\mathcal{O}(N)$$ χρόνο. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα +και γι' αυτό το υποπρόβλημα. ## Υποπρόβλημα 4 ($$L = R$$) --- Λύση $$\mathcal{O}(N + Q)$$ @@ -203,8 +206,10 @@ $$\text{supertree\_loop\_opt}[u]$$, ώστε να την έχουμε διαθέ Μάλιστα, οι τιμές αυτές μοιάζουν πολύ με τις τιμές $$\text{supertree\_root\_opt}$$ που ορίσαμε στο προηγούμενο υποπρόβλημα, με τη διαφορά όμως ότι δεν είναι πλέον αναγκαίο -να φτάσουμε μέχρι τη ρίζα, αλλά πρέπει να γυρίσουμε πίσω στην κορυφή $$u$$. Ο τύπος -που είχαμε προηγούμενως αλλάζει ως εξής: +να φτάσουμε μέχρι τη ρίζα, αλλά πρέπει να γυρίσουμε πίσω στην κορυφή $$u$$, +το οποίο σημαίνει ότι πρέπει να μετρήσουμε το κόστος του δρόμου +$$(u, \text{parent}(u))$$ **δύο φορές**. Ο τύπος που είχαμε προηγούμενως +αλλάζει ως εξής: $$ \begin{align*} @@ -212,7 +217,7 @@ $$ ( & \text{supertree\_loop\_opt}[\text{parent}(u)] + \text{subtree\_loop\_opt}[\text{parent}(u)] \\ &- (\text{subtree\_loop\_opt}[u] - 2 \cdot w_{u, \text{parent}(u)})^+ - - 2 \cdot w_{u, \text{parent}(u)} )^+ + - \textcolor{red}{2} \cdot w_{u, \text{parent}(u)} )^+ \end{align*} $$ @@ -222,7 +227,7 @@ $$\text{supertree\_loop\_opt}$$ με ρίζα την κορυφή 1 μπορεί ερωτήματα, κι έπειτα για κάθε ερώτημα, μπορούμε να υπολογίσουμε την απάντηση σε σταθερό χρόνο με τον τύπο: -$$\text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]$$. +$$ \text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]. $$ Συνολικά η λύση αυτή έχει χρονική πολυπλοκότητα $$\mathcal{O}(N + Q)$$. @@ -259,7 +264,7 @@ $$\text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]$$. όπως στον ορισμό του $$\text{supertree\_loop\_opt}$$ παραπάνω). Για την ώρα, ας υποθέσουμε ότι γνωρίζουμε τον ελάχιστο κοινό πρόγονο $$z = -LCA(L, R)$$. +\text{LCA}(L, R)$$. Έχουμε τις παρακάτω περιπτώσεις: @@ -343,4 +348,4 @@ LCA(L, R)$$. Η λύση που περιγράψαμε έχει συνολική πολυπλοκότητα $$\mathcal{O} ( (N + Q) \log N )$$ η οποία μας καλύπτει για τους περιορισμούς -του προβλήματος. \ No newline at end of file +του προβλήματος. From 3f9178d20d43b739e7e8f60c2ed514c32985c84d Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Fri, 21 Mar 2025 11:32:40 -0700 Subject: [PATCH 10/16] Use global vars on all subtasks --- .../code/37-PDP/tiphunting/optimal.cc | 19 +++---- .../code/37-PDP/tiphunting/subtask1.cc | 4 +- .../code/37-PDP/tiphunting/subtask2.cc | 24 ++++----- .../code/37-PDP/tiphunting/subtask3.cc | 44 ++++++++-------- .../code/37-PDP/tiphunting/subtask4.cc | 42 ++++++++------- .../code/37-PDP/tiphunting/subtask5.cc | 52 ++++++++++--------- 6 files changed, 95 insertions(+), 90 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc index 9a8971b5..6fe3be45 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -2,13 +2,6 @@ #include #include #include -// #define DEBUG - -#ifdef DEBUG -#define debug(...) fprintf(stderr, __VA_ARGS__) -#else -#define debug(...) ((void)0) -#endif using namespace std; @@ -75,7 +68,8 @@ void compute_subtree_loop_opt(long u) { void compute_supertree_root_opt(long u) { supertree_root_opt[u] = 0; - if (parent[u] != -1) + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent[u] != u) supertree_root_opt[u] = subtree_loop_opt[parent[u]] + supertree_root_opt[parent[u]] - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) - parent_weight[u]; @@ -96,7 +90,8 @@ void compute_supertree_root_opt(long u) { void compute_supertree_loop_opt(int u) { supertree_loop_opt[u] = 0; - if (parent[u] != -1) + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent[u] != u) supertree_loop_opt[u] = positive_part(subtree_loop_opt[parent[u]] + supertree_loop_opt[parent[u]] - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) - 2*parent_weight[u]); @@ -210,11 +205,13 @@ int main() { compute_pred(H); subtree_loop_opt.resize(n); + compute_subtree_loop_opt(0); + supertree_loop_opt.resize(n); + compute_supertree_loop_opt(0); + supertree_root_opt.resize(n); - compute_subtree_loop_opt(0); compute_supertree_root_opt(0); - compute_supertree_loop_opt(0); for (long i = 0; i < q; ++i) { long L, R; diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc index 5994effb..81f31ccc 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask1.cc @@ -24,8 +24,8 @@ int main() { } for (long i = 0; i < q; ++i) { - long src, dst; - scanf("%li%li", &src, &dst); + long L, R; + scanf("%li%li", &L, &R); printf("%lli\n", sol); } diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc index 58c18332..1717628b 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc @@ -5,20 +5,24 @@ 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 subtree_loop_opt(const vvll& tree, const vector& tip, long u, long parent) { +long long subtree_loop_opt(long u, long parent) { long long sol = tip[u]; for (auto [v, w]: tree[u]) { if (v == parent) continue; - long long s = subtree_loop_opt(tree, tip, v, u); + long long s = subtree_loop_opt(v, u); sol += positive_part(s - 2*w); } @@ -32,33 +36,29 @@ int main() { long n, q; scanf("%li%li", &n, &q); - assert(1 <= n && n <= 1'000); - assert(1 <= q && q <= 1'000); - vector tip(n); + 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`. - vvll tree(n); + tree.resize(n); for (long i = 0; i < n-1; ++i) { long u, v, w; scanf("%li%li%li", &u, &v, &w); - assert(1 <= u && u <= n); - assert(1 <= v && v <= n); tree[u-1].push_back({v-1, w}); tree[v-1].push_back({u-1, w}); } for (long i = 0; i < q; ++i) { - long src, dst; - scanf("%li%li", &src, &dst); - assert(src == dst); + long L, R; + scanf("%li%li", &L, &R); + assert(L == R); - printf("%lli\n", subtree_loop_opt(tree, tip, src-1, -1)); + printf("%lli\n", subtree_loop_opt(L-1, L-1)); } return 0; diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc index 2fbeaa37..94cdc1c9 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc @@ -5,8 +5,13 @@ using namespace std; using ll = pair; +using vl = vector; using vvll = vector>; +vvll tree; +vl tip; +vector subtree_loop_opt, supertree_root_opt; + long long positive_part(long long x) { return max(0LL, x); } // Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` @@ -16,13 +21,12 @@ long long positive_part(long long x) { return max(0LL, x); } // και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& tree, const vector& tip, long u, long parent) { +void compute_subtree_loop_opt(long u, long parent) { subtree_loop_opt[u] = tip[u]; - int x; for (auto [v, w]: tree[u]) { if (v == parent) continue; - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + compute_subtree_loop_opt(v, u); subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); } } @@ -35,18 +39,16 @@ void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& t // από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { - assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long long)tree.size()); - +void compute_supertree_root_opt(long u, long parent, long w) { supertree_root_opt[u] = 0; - if (parent != -1) + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent != u) supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; for (auto [v, w]: tree[u]) if (v != parent) - compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, v, u, w); + compute_supertree_root_opt(v, u, w); } int main() { @@ -57,35 +59,35 @@ int main() { long n, q; scanf("%li%li", &n, &q); - vector tip(n); + 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`. - vvll tree(n); + tree.resize(n); for (long i = 0; i < n-1; ++i) { long u, v, w; scanf("%li%li%li", &u, &v, &w); - assert(1 <= u && u <= n); - assert(1 <= v && v <= n); tree[u-1].push_back({v-1, w}); tree[v-1].push_back({u-1, w}); } + subtree_loop_opt.resize(n); + supertree_root_opt.resize(n); + for (long i = 0; i < q; ++i) { - long src, dst; - scanf("%li%li", &src, &dst); - src -= 1; - dst -= 1; + long L, R; + scanf("%li%li", &L, &R); + L -= 1; + R -= 1; - vector subtree_loop_opt(n), supertree_root_opt(n); - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, dst, -1); - compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, dst, -1, -1); + compute_subtree_loop_opt(R, R); + compute_supertree_root_opt(R, R, 0); - printf("%lli\n", subtree_loop_opt[src] + supertree_root_opt[src]); + printf("%lli\n", subtree_loop_opt[L] + supertree_root_opt[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 index ce894e1d..6885069c 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc @@ -5,8 +5,13 @@ using namespace std; using ll = pair; +using vl = vector; using vvll = vector>; +vvll tree; +vl tip; +vector subtree_loop_opt, supertree_loop_opt; + long long positive_part(long long x) { return max(0LL, x); } // Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` @@ -16,12 +21,12 @@ long long positive_part(long long x) { return max(0LL, x); } // και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& tree, const vector& tip, long u, long parent) { +void compute_subtree_loop_opt(long u, long parent) { subtree_loop_opt[u] = tip[u]; for (auto [v, w]: tree[u]) { if (v == parent) continue; - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + compute_subtree_loop_opt(v, u); subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); } } @@ -33,18 +38,16 @@ void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& t // supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά // ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που // ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. -void compute_supertree_loop_opt(vector& supertree_loop_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { - assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long long)tree.size()); - +void compute_supertree_loop_opt(long u, long parent, long w) { supertree_loop_opt[u] = 0; - if (parent != -1) + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent != u) supertree_loop_opt[u] = positive_part(subtree_loop_opt[parent] + supertree_loop_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - 2*w); for (auto [v, w]: tree[u]) if (v != parent) - compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, v, u, w); + compute_supertree_loop_opt(v, u, w); } int main() { @@ -55,35 +58,34 @@ int main() { long n, q; scanf("%li%li", &n, &q); - vector tip(n); + 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`. - vvll tree(n); + tree.resize(n); for (long i = 0; i < n-1; ++i) { long u, v, w; scanf("%li%li%li", &u, &v, &w); - assert(1 <= u && u <= n); - assert(1 <= v && v <= n); tree[u-1].push_back({v-1, w}); tree[v-1].push_back({u-1, w}); } - vector subtree_loop_opt(n); - vector supertree_loop_opt(n); - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, 0, -1); - compute_supertree_loop_opt(supertree_loop_opt, subtree_loop_opt, tree, 0, -1, -1); + subtree_loop_opt.resize(n); + compute_subtree_loop_opt(0, 0); + + supertree_loop_opt.resize(n); + compute_supertree_loop_opt(0, 0, 0); for (long i = 0; i < q; ++i) { - long src, dst; - scanf("%li%li", &src, &dst); - assert(src == dst); + long L, R; + scanf("%li%li", &L, &R); + assert(L == R); - printf("%lli\n", subtree_loop_opt[src-1] + supertree_loop_opt[src-1]); + printf("%lli\n", subtree_loop_opt[L-1] + supertree_loop_opt[L-1]); } return 0; diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc index 631417a1..c540c389 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc @@ -5,8 +5,13 @@ using namespace std; using ll = pair; +using vl = vector; using vvll = vector>; +vvll tree; +vl tip; +vector subtree_loop_opt, supertree_root_opt; + long long positive_part(long long x) { return max(0LL, x); } // Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` @@ -16,12 +21,12 @@ long long positive_part(long long x) { return max(0LL, x); } // και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& tree, const vector& tip, long u, long parent) { +void compute_subtree_loop_opt(long u, long parent) { subtree_loop_opt[u] = tip[u]; for (auto [v, w]: tree[u]) { if (v == parent) continue; - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, v, u); + compute_subtree_loop_opt(v, u); subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); } } @@ -34,18 +39,16 @@ void compute_subtree_loop_opt(vector& subtree_loop_opt, const vvll& t // από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(vector& supertree_root_opt, const vector& subtree_loop_opt, const vvll& tree, long u, long parent, long w) { - assert(0 <= u && u < tree.size()); - assert(-1 <= parent && parent < (long long)tree.size()); - +void compute_supertree_root_opt(long u, long parent, long w) { supertree_root_opt[u] = 0; - if (parent != -1) + // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. + if (parent != u) supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; for (auto [v, w]: tree[u]) if (v != parent) - compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, v, u, w); + compute_supertree_root_opt(v, u, w); } int main() { @@ -56,42 +59,43 @@ int main() { long n, q; scanf("%li%li", &n, &q); - vector tip(n); + 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`. - vvll tree(n); + tree.resize(n); for (long i = 0; i < n-1; ++i) { long u, v, w; scanf("%li%li%li", &u, &v, &w); - assert(1 <= u && u <= n); - assert(1 <= v && v <= n); tree[u-1].push_back({v-1, w}); tree[v-1].push_back({u-1, w}); } - long src, dst; - scanf("%li%li", &src, &dst); - src -= 1; - dst -= 1; + long L, R; + scanf("%li%li", &L, &R); + L -= 1; + R -= 1; + + subtree_loop_opt.resize(n); + compute_subtree_loop_opt(L, L);; - vector subtree_loop_opt(n), supertree_root_opt(n); - compute_subtree_loop_opt(subtree_loop_opt, tree, tip, src, -1); - compute_supertree_root_opt(supertree_root_opt, subtree_loop_opt, tree, src, -1, -1); + supertree_root_opt.resize(n); + compute_supertree_root_opt(L, L, 0); // Απάντηση στο πρώτο ερώτημα. - printf("%lli\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); + printf("%lli\n", subtree_loop_opt[R] + supertree_root_opt[R]); // Απάντηση στα υπόλοιπα `q-1` ερωτήματα. for (long i = 1; i < q; ++i) { - scanf("%li%li", &src, &dst); - src -= 1; - dst -= 1; - printf("%lli\n", subtree_loop_opt[dst] + supertree_root_opt[dst]); + long new_L; + scanf("%li%li", &new_L, &R); + assert(L == new_L - 1); + R -= 1; + printf("%lli\n", subtree_loop_opt[R] + supertree_root_opt[R]); } return 0; From d0907779034c6a18074d8dcdb5e955f26d4cb0cc Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Fri, 21 Mar 2025 11:36:50 -0700 Subject: [PATCH 11/16] Fix typos --- _includes/source_code/code/37-PDP/tiphunting/subtask2.cc | 2 +- _includes/source_code/code/37-PDP/tiphunting/subtask3.cc | 4 ++-- _includes/source_code/code/37-PDP/tiphunting/subtask4.cc | 2 +- _includes/source_code/code/37-PDP/tiphunting/subtask5.cc | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc index 1717628b..433be6dd 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc @@ -14,7 +14,7 @@ vl tip; long long positive_part(long long x) { return max(0LL, x); } // Επιστρέφει το κέρδος της βέλτιστης διαδρομή η οποία ξεκινάει -// από την κορυφή `u`, καταλλήγει πίσω σε αυτή και παραμένει +// από την κορυφή `u`, καταλήγει πίσω σε αυτή και παραμένει // εξ' ολοκλήρου μέσα στο υποδέντρο που ορίζει η `u` -- με άλλα λόγια, // η διαδρομή απαγορεύεται να διασχίσει το δρόμο `(u, parent)`. long long subtree_loop_opt(long u, long parent) { diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc index 94cdc1c9..19c32a11 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc @@ -18,7 +18,7 @@ long long positive_part(long long x) { return max(0LL, x); } // κι όλους τους απογόνους της. // // subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. void compute_subtree_loop_opt(long u, long parent) { @@ -36,7 +36,7 @@ void compute_subtree_loop_opt(long u, long parent) { // `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. // // supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και +// από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. void compute_supertree_root_opt(long u, long parent, long w) { diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc index 6885069c..1c6fa92b 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc @@ -18,7 +18,7 @@ long long positive_part(long long x) { return max(0LL, x); } // κι όλους τους απογόνους της. // // subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. void compute_subtree_loop_opt(long u, long parent) { diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc index c540c389..89785f00 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc @@ -18,7 +18,7 @@ long long positive_part(long long x) { return max(0LL, x); } // κι όλους τους απογόνους της. // // subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει +// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει // η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. void compute_subtree_loop_opt(long u, long parent) { @@ -36,7 +36,7 @@ void compute_subtree_loop_opt(long u, long parent) { // `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. // // supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// από την κορυφή `u`, καταλλήγει στη ρίζα τους δέντρου και +// από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και // μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής // `u` ΔΕΝ προσμετράται. void compute_supertree_root_opt(long u, long parent, long w) { From 62ce7172b31439af0680468f6e09092ea377291d Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Fri, 21 Mar 2025 11:38:53 -0700 Subject: [PATCH 12/16] Fix typos --- _data/contests/37-PDP.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_data/contests/37-PDP.yml b/_data/contests/37-PDP.yml index 339fdc28..e63c636f 100644 --- a/_data/contests/37-PDP.yml +++ b/_data/contests/37-PDP.yml @@ -91,5 +91,5 @@ tiphunting: solution: true solution_author: "Μάκης Αρσένης" codes_in_git: true - solution_tags: [lca, dfs, tree, binary lifting] + solution_tags: [lca, dfs, trees, binary lifting] on_judge: false From 40613fedf7ab437b07ad3914b322740cb202c1db Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Fri, 21 Mar 2025 15:50:02 -0700 Subject: [PATCH 13/16] Add code links --- contests/_37-PDP/b-tiphunting-solution.md | 42 +++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/contests/_37-PDP/b-tiphunting-solution.md b/contests/_37-PDP/b-tiphunting-solution.md index f2fc1b3e..8e3d5825 100644 --- a/contests/_37-PDP/b-tiphunting-solution.md +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -3,18 +3,6 @@ layout: solution codename: tiphunting --- - - ## Επεξήγηση Εκφώνησης Μας δίνεται η περιγραφή ενός οδικού δικτύου μιας πόλης με $$N$$ σπίτια που @@ -51,6 +39,9 @@ _διαδρομή_ ορίζουμε μια πεπερασμένη ακολουθ Η απάντηση λοιπόν σε κάθε ερώτημα είναι η ίδια και ίση με το συνολικό άθροισμα $$S = \sum_{i = 1}^N t_i$$ των φιλοδωρημάτων. +Δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md +solution_name='subtask1.cc' %}) + ## Γενικές Παρατηρήσεις Πριν προχωρήσουμε, ας παρατηρήσουμε κάποιες ιδιότητες της βέλτιστης λύσης που @@ -103,9 +94,8 @@ $$u$$. Διαφορετικά η επιλογή 2 είναι καλύτερη --- ή ισοδύναμη σε περίπτωση μηδενικού κέρδους. - Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{subtree\_loop\_opt}$$ -αναδρομικά εφαρμόζοντας τον παρακάτω τύπο: +εφαρμόζοντας τον παρακάτω αναδρομικό τύπο: $$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} \left( \text{subtree\_loop\_opt}[v] - 2 \cdot w_{u, v} \right)^+ $$ @@ -113,6 +103,8 @@ $$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} όπου ο συμβολισμός $$(x)^+$$ σημαίνει $$\max(0, x)$$ και $$\text{children}(u)$$ είναι το σύνολο με όλα τα παιδιά της κορυφής $$u$$. +{% include code.md solution_name='subtask2.cc' start=16 end=30 %} + Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{subree\_loop\_opt}[L]$$. Για τον υπολογισμό όλων των παραπάνω τιμών, χρειάζεται για κάθε κορυφή να @@ -122,6 +114,9 @@ $$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} πολυπλοκότητα $$\mathcal{O}(N Q)$$, η οποία μας καλύπτει για τους περιορισμούς αυτού του υποπροβλήματος. +Η πλήρης κώδικας βρίσκεται [εδώ]({% include link_to_source.md +solution_name='subtask2.cc' %}) + ## Υποπρόβλημα 3 ($$N \le 1.000, Q \le 1.000$$) --- Λύση $$\mathcal{O}(N Q)$$ Ας σκεφτόυμε τώρα την πιο γενική περίπτωση όπου η διαδρομή μας θα πρέπει να @@ -176,11 +171,17 @@ $$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ τ όταν αυτή έχει θετικό πρόσημο. Τέλος, σε κάθε περίπτωση, συμπεριλαμβάνουμε και το κόστος διάσχισης του δρόμου $$(u, \text{parent}[u])$$ ακριβώς μία φορά. +Μια αναδρομική υλοποίηση είναι η παρακάτω: + +{% include code.md solution_name='subtask3.cc' start=17 end=52 %} + Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διασχίσεις_ του δέντρου (μια για τον υπολογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα μια για τον υπολογισμό των $$\text{supertree\_root\_opt}$$), οι οποίες γίνονται σε $$\mathcal{O}(N)$$ χρόνο. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα και γι' αυτό το υποπρόβλημα. +Μπορείτε να δείτε ολόκληρο των κώδικα [εδώ]({% include link_to_source.md +solution_name='subtask3.cc' %}). ## Υποπρόβλημα 4 ($$L = R$$) --- Λύση $$\mathcal{O}(N + Q)$$ @@ -230,6 +231,8 @@ $$\text{supertree\_loop\_opt}$$ με ρίζα την κορυφή 1 μπορεί $$ \text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]. $$ Συνολικά η λύση αυτή έχει χρονική πολυπλοκότητα $$\mathcal{O}(N + Q)$$. +Μπορείτε να δείτε ολόκληρο των κώδικα [εδώ]({% include link_to_source.md +solution_name='subtask4.cc' %}). ## Υποπρόβλημα 5 ($$L_1 = L_2 = \ldots = L_N$$) --- Λύση $$\mathcal{O}(Ν + Q)$$ @@ -239,10 +242,12 @@ $$ \text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]. $$ και να εφαρμόσουμε τη λύση του υποπροβλήματος 3, υπολογίζοντας όμως τις βοηθητικές τιμές $$\text{subtree\_loop\_opt}$$ και $$\text{supertree\_root\_opt}$$ μόνο μια φορά στην αρχή, και απαντώντας μετά το κάθε ερώτημα σε σταθερό χρόνο. +Ο πλήρης κώδικας βρίσκεται [εδώ]({% include link_to_source.md +solution_name='subtask5.cc' %}). -**Σημείωση**: Στο υποπρόβλημα 3 θεωρήσαμε ότι η ρίζα ήταν το $$R$$, όμως λόγω -συμμετρίας του προβλήματος, θα μπορούσαμε να είχαμε θεωρήσει ως ρίζα το $$L$$ όπως -κάνουμε σε αυτό το υποπρόβλημα. +**Σημείωση**: Στο υποπρόβλημα 3 θεωρήσαμε ότι η ρίζα ήταν το σπίτι $$R$$, όχι +το $$L$$ όπως εδώ. Η Παρατήρηση 3 όμως μας εξασφαλίζει ότι αυτή η αλλαγή +δεν επηρεάζει την απάντηση. ## Υποπρόβλημα 6 @@ -344,7 +349,8 @@ $$ \text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]. $$ κάνει κατάλληλη προεργασία σε χρόνο $$\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 )$$ η οποία μας καλύπτει για τους περιορισμούς From 1e1f1a172c57274f08abfe4381bf66fa97fc021c Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Fri, 21 Mar 2025 15:50:13 -0700 Subject: [PATCH 14/16] Add TASK file for tiphunting --- .../source_code/code/37-PDP/tiphunting/TASK | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 _includes/source_code/code/37-PDP/tiphunting/TASK 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..c2774fbe --- /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++", + ), + ] +) From 08730988e6dfc96b786fb96e319716d65fb6f2cc Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Sat, 22 Mar 2025 15:36:53 -0700 Subject: [PATCH 15/16] Respond to PR comments --- .../source_code/code/37-PDP/tiphunting/TASK | 10 +-- .../code/37-PDP/tiphunting/optimal.cc | 80 ++++++++----------- .../code/37-PDP/tiphunting/subtask2.cc | 7 +- .../code/37-PDP/tiphunting/subtask3.cc | 16 ++-- .../code/37-PDP/tiphunting/subtask4.cc | 15 ++-- .../code/37-PDP/tiphunting/subtask5.cc | 18 +++-- contests/_37-PDP/b-tiphunting-solution.md | 46 +++++------ 7 files changed, 94 insertions(+), 98 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/TASK b/_includes/source_code/code/37-PDP/tiphunting/TASK index c2774fbe..cc0ad524 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/TASK +++ b/_includes/source_code/code/37-PDP/tiphunting/TASK @@ -13,31 +13,31 @@ TASK( passes_only = [2, 3, 4, 5], lang = "c++", ), - SOLUTION( + SOLUTION( name = "subtask2", source = "subtask2.cc", passes_only = [6, 7, 8, 9, 10], lang = "c++", ), - SOLUTION( + SOLUTION( name = "subtask3", source = "subtask3.cc", passes_only = [1, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], lang = "c++", ), - SOLUTION( + SOLUTION( name = "subtask4", source = "subtask4.cc", passes_only = [6, 7, 8, 9, 10, 16, 17, 18, 19, 20, 21], lang = "c++", ), - SOLUTION( + SOLUTION( name = "subtask5", source = "subtask5.cc", passes_only = [22, 23, 24, 25, 26, 27], lang = "c++", ), - SOLUTION( + SOLUTION( name = "subtask6", source = "optimal.cc", passes_all, diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc index 6fe3be45..de108cd8 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -18,6 +18,27 @@ vector subtree_loop_opt, supertree_loop_opt, supertree_root_opt; 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)); + subtree_loop_opt.resize(n); + supertree_loop_opt.resize(n); + supertree_root_opt.resize(n); +} + // Διασχίζει το δέντρο `tree` ξεκινώντας από την κορυφή `u` και υπολογίζει // αναδρομικά τις τιμές `depth[v]`, `parent[v]` και `parent_weight[v]` για κάθε // κορυφή `v != u` στο υποδέντρο της `u`. Οι τιμές `depth[u]`, `parent[u]` και @@ -108,8 +129,9 @@ void compute_supertree_loop_opt(int u) { // O caller θα πρέπει να έχει ήδη υπολογίσει τον πίνακα parent // (δες `compute_auxiliary`) έτσι ώστε η τιμή `parent[u]` να είναι // ο γονέας της `u`, εκτός από την ρίζα `r` για την οποία `r == parent[r]`. -void compute_pred(long H) { +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]; @@ -147,7 +169,7 @@ lll lca(long u, long v) { if (pred[0][u] == v) return { v, u, -1 }; - u =pred[0][u]; + u = pred[0][u]; } for (long h = H; h >= 0; --h) { @@ -167,14 +189,14 @@ int main() { long n, q; scanf("%li%li", &n, &q); - tip.resize(n); + 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`. - tree.resize(n); for (long i = 0; i < n-1; ++i) { long u, v, w; scanf("%li%li%li", &u, &v, &w); @@ -183,41 +205,18 @@ int main() { tree[v-1].push_back({u-1, w}); } - // Αρχικοποιώντας `depth[0] = 0`, `parent[0] = 0` θέτουμε την κορυφή - // 0 ως ρίζα του δέντρου. Η συνάρτηση `compute_auxiliary` συμπληρώνει - // τις τιμές και για τους υπόλοιπους κόμβους. - depth.resize(n, 0); - parent.resize(n, 0); - parent_weight.resize(n, 0); compute_auxiliary(0); - // Θα χρειαστούμε το μέγιστο βάθος ώστε να υπολογίσουμε τις διαστάσεις - // πίνακα `pred` παρακάτω. - long max_depth = 0; - for (long i = 0; i < n; ++i) - max_depth = max(max_depth, depth[i]); - - // Υπολογισμός του πίνακα `pred` από τον πίνακα `parent`. - // Το δέντρο έχει ύψος `max_depth` επομένως θα χρειαστούμε τους - // απογόνους (predecessors) το πολύ μέχρι `max_depth <= 2^H` επίπεδα παραπάνω. - const long H = long(ceil(log2(max_depth))); - pred.resize(H+1, vector(n, 0)); - compute_pred(H); + compute_pred(); - subtree_loop_opt.resize(n); compute_subtree_loop_opt(0); - - supertree_loop_opt.resize(n); compute_supertree_loop_opt(0); - - supertree_root_opt.resize(n); compute_supertree_root_opt(0); for (long i = 0; i < q; ++i) { long L, R; scanf("%li%li", &L, &R); - L -= 1; - R -= 1; + L--, R--; if (L == R) { printf("%lli\n", subtree_loop_opt[L] + supertree_loop_opt[L]); @@ -241,22 +240,13 @@ int main() { assert(pred[0][u] == z); assert(pred[0][v] == z); - // (a) - sol = supertree_root_opt[L] - supertree_root_opt[u] + subtree_loop_opt[L] ; - - // (b) - sol += supertree_root_opt[R] - supertree_root_opt[v] + subtree_loop_opt[R]; - - // (c) - sol += subtree_loop_opt[z]; - sol -= positive_part(subtree_loop_opt[u] - 2*parent_weight[u]); - sol -= positive_part(subtree_loop_opt[v] - 2*parent_weight[v]); - - // (d) - sol += supertree_loop_opt[z]; - - // (e) - sol -= (parent_weight[u] + parent_weight[v]); + sol = supertree_root_opt[L] - supertree_root_opt[u] + subtree_loop_opt[L] // (a) + + supertree_root_opt[R] - supertree_root_opt[v] + subtree_loop_opt[R] // (b) + + subtree_loop_opt[z] // (c1) + - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) // (c2) + - positive_part(subtree_loop_opt[v] - 2*parent_weight[v]) // (c3) + + supertree_loop_opt[z] // (d) + - (parent_weight[u] + parent_weight[v]); // (e) } printf("%lli\n", sol); diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc index 433be6dd..fd4d5a6b 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc @@ -13,9 +13,9 @@ vl tip; long long positive_part(long long x) { return max(0LL, x); } -// Επιστρέφει το κέρδος της βέλτιστης διαδρομή η οποία ξεκινάει +// Επιστρέφει το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // από την κορυφή `u`, καταλήγει πίσω σε αυτή και παραμένει -// εξ' ολοκλήρου μέσα στο υποδέντρο που ορίζει η `u` -- με άλλα λόγια, +// εξ' ολοκλήρου μέσα στο υποδέντρο που ορίζει η `u`. Με άλλα λόγια, // η διαδρομή απαγορεύεται να διασχίσει το δρόμο `(u, parent)`. long long subtree_loop_opt(long u, long parent) { long long sol = tip[u]; @@ -56,9 +56,10 @@ int main() { for (long i = 0; i < q; ++i) { long L, R; scanf("%li%li", &L, &R); + L--, R--; assert(L == R); - printf("%lli\n", subtree_loop_opt(L-1, L-1)); + printf("%lli\n", subtree_loop_opt(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 index 19c32a11..03c024f3 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc @@ -14,12 +14,12 @@ vector subtree_loop_opt, supertree_root_opt; long long positive_part(long long x) { return max(0LL, x); } -// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// Πρώτη διάσχιση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` // κι όλους τους απογόνους της. // // subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// η κορυφή `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. void compute_subtree_loop_opt(long u, long parent) { subtree_loop_opt[u] = tip[u]; @@ -31,9 +31,9 @@ void compute_subtree_loop_opt(long u, long parent) { } } -// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή +// Δεύτερη διάσχιση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή // `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διάσχιση. // // supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και @@ -44,7 +44,9 @@ void compute_supertree_root_opt(long u, long parent, long w) { // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. if (parent != u) - supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; + supertree_root_opt[u] = + subtree_loop_opt[parent] + supertree_root_opt[parent] + - positive_part(subtree_loop_opt[u] - 2*w) - w; for (auto [v, w]: tree[u]) if (v != parent) @@ -81,9 +83,7 @@ int main() { for (long i = 0; i < q; ++i) { long L, R; scanf("%li%li", &L, &R); - L -= 1; - R -= 1; - + L--, R--; compute_subtree_loop_opt(R, R); compute_supertree_root_opt(R, R, 0); diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc index 1c6fa92b..e21a3deb 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc @@ -14,12 +14,12 @@ vector subtree_loop_opt, supertree_loop_opt; long long positive_part(long long x) { return max(0LL, x); } -// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// Πρώτη διάσχιση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` // κι όλους τους απογόνους της. // // subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// η κορυφή `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. void compute_subtree_loop_opt(long u, long parent) { subtree_loop_opt[u] = tip[u]; @@ -31,9 +31,9 @@ void compute_subtree_loop_opt(long u, long parent) { } } -// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_loop_opt` για την κορυφή +// Δεύτερη διάσχιση η οποία υπολογίζει το `supertree_loop_opt` για την κορυφή // `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διάσχιση. // // supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά // ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που @@ -43,7 +43,9 @@ void compute_supertree_loop_opt(long u, long parent, long w) { // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. if (parent != u) - supertree_loop_opt[u] = positive_part(subtree_loop_opt[parent] + supertree_loop_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - 2*w); + supertree_loop_opt[u] = + positive_part(subtree_loop_opt[parent] + supertree_loop_opt[parent] + - positive_part(subtree_loop_opt[u] - 2*w) - 2*w); for (auto [v, w]: tree[u]) if (v != parent) @@ -83,9 +85,10 @@ int main() { for (long i = 0; i < q; ++i) { long L, R; scanf("%li%li", &L, &R); + L--, R--; assert(L == R); - printf("%lli\n", subtree_loop_opt[L-1] + supertree_loop_opt[L-1]); + printf("%lli\n", subtree_loop_opt[L] + supertree_loop_opt[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 index 89785f00..ee19af94 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc @@ -14,12 +14,12 @@ vector subtree_loop_opt, supertree_root_opt; long long positive_part(long long x) { return max(0LL, x); } -// Πρώτη διαπέραση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// Πρώτη διάσχιση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` // κι όλους τους απογόνους της. // // subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u` -- με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει +// η κορυφή `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει // τον δρόμο `(u, parent)`. void compute_subtree_loop_opt(long u, long parent) { subtree_loop_opt[u] = tip[u]; @@ -31,9 +31,9 @@ void compute_subtree_loop_opt(long u, long parent) { } } -// Δεύτερη διαπέραση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή +// Δεύτερη διάσχιση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή // `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διαπέραση. +// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διάσχιση. // // supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει // από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και @@ -44,7 +44,9 @@ void compute_supertree_root_opt(long u, long parent, long w) { // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. if (parent != u) - supertree_root_opt[u] = subtree_loop_opt[parent] + supertree_root_opt[parent] - positive_part(subtree_loop_opt[u] - 2*w) - w; + supertree_root_opt[u] = + subtree_loop_opt[parent] + supertree_root_opt[parent] + - positive_part(subtree_loop_opt[u] - 2*w) - w; for (auto [v, w]: tree[u]) if (v != parent) @@ -77,8 +79,7 @@ int main() { long L, R; scanf("%li%li", &L, &R); - L -= 1; - R -= 1; + L--, R--; subtree_loop_opt.resize(n); compute_subtree_loop_opt(L, L);; @@ -94,7 +95,8 @@ int main() { long new_L; scanf("%li%li", &new_L, &R); assert(L == new_L - 1); - R -= 1; + R--; + printf("%lli\n", subtree_loop_opt[R] + supertree_root_opt[R]); } diff --git a/contests/_37-PDP/b-tiphunting-solution.md b/contests/_37-PDP/b-tiphunting-solution.md index 8e3d5825..dd7123d5 100644 --- a/contests/_37-PDP/b-tiphunting-solution.md +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -36,7 +36,7 @@ _διαδρομή_ ορίζουμε μια πεπερασμένη ακολουθ Σε αυτό το υποπρόβλημα το κόστος διάσχισης κάθε δρόμου είναι μηδενικό, επομένως η διαδρομή μας μπορεί να επισκεφτεί όλα τα σπίτια και να μαζέψει όλα τα φιλοδωρήματα, ανεξαρτήτως αφετηρίας και προορισμού. -Η απάντηση λοιπόν σε κάθε ερώτημα είναι η ίδια και ίση με το συνολικό +Η απάντηση λοιπόν σε κάθε ερώτημα είναι η ίδια, και ίση με το συνολικό άθροισμα $$S = \sum_{i = 1}^N t_i$$ των φιλοδωρημάτων. Δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md @@ -45,7 +45,7 @@ solution_name='subtask1.cc' %}) ## Γενικές Παρατηρήσεις Πριν προχωρήσουμε, ας παρατηρήσουμε κάποιες ιδιότητες της βέλτιστης λύσης που -θα μας φανούν χρήσιμα σε όλα τα υποπροβλήματα που ακολουθούν. +θα μας φανούν χρήσιμες σε όλα τα υποπροβλήματα που ακολουθούν. **Παρατήρηση 1**: Στη βέλτιστη λύση δεν θα χρειαστεί ποτέ να διασχίσουμε κάποιο δρόμο πάνω από @@ -63,14 +63,14 @@ solution_name='subtask1.cc' %}) θα τον έχουμε διασχίσει είτε καμία είτε δύο φορές. **Παρατήρηση 3**: -Η απάντηση στο ερώτημα $$L, R$$ είναι η ίδια με την απάντηση στο ερώτημα $$R, +Η απάντηση στο ερώτημα $$L, R$$ είναι ίδια με την απάντηση στο ερώτημα $$R, L$$ λόγω συμμετρίας (οι δρόμοι είναι διπλής κατεύθυνσης). -## Υποπρόβλημα 2 ($$N \le 1.000, Q \le 1.000, L = R$$) --- Λύση $$\mathcal{O}(N Q)$$ +## Υποπρόβλημα 2 ($$N \le 1.000, Q \le 1.000, L = R$$) --- Λύση $$\mathcal{O}(N \cdot Q)$$ Ο περιορισμός $$L = R$$ απλουστεύει το πρόβλημα καθώς δεν χρειάζεται να ασχοληθούμε με το μέρος της λύσης που περιλαμβάνει το μονοπάτι που αναφέραμε -προηγουμένως, παρά μόνο με την επιμέρους εξερεύνηση. +στην Παρατήρηση 2, παρά μόνο με την επιμέρους εξερεύνηση. Ας φανταστούμε ότι το σπίτι $$L$$ από το οποίο ξεκινάμε είναι η ρίζα του δέντρου που αναπαριστά το οδικό δίκτυο. Κάθε σπίτι $$u$$ που συνδέεται άμεσα με @@ -89,8 +89,8 @@ $$\text{subtree}(u)$$. Για κάθε ένα από αυτά τα δέντρα στο υποδέντρο που ορίζει το σπίτι $$u$$, και τελικά επιστρέψουμε πάλι στο $$u$$. -Παρατηρήστε ότι η επιλογή 1 είναι προτιμότερη όταν το κέρδος της είναι -θετικό, δηλαδή όταν $$\text{subtree\_loop\_opt}[u] - 2 \cdot w \ge 0$$. +Είναι φανερό ότι θα ακολουθήσουμε την επιλογή 1 όταν αυτή παράγει κέρδος, +δηλαδή όταν $$\text{subtree\_loop\_opt}[u] - 2\cdot w \ge 0$$. Διαφορετικά η επιλογή 2 είναι καλύτερη --- ή ισοδύναμη σε περίπτωση μηδενικού κέρδους. @@ -105,21 +105,21 @@ $$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} {% include code.md solution_name='subtask2.cc' start=16 end=30 %} -Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{subree\_loop\_opt}[L]$$. +Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{subtree\_loop\_opt}[L]$$. Για τον υπολογισμό όλων των παραπάνω τιμών, χρειάζεται για κάθε κορυφή να υπολογίσουμε ένα άθροισμα που περιλαμβάνει όλα τα παιδιά της, συνεπώς χρειάζεται χρόνος γραμμικός στο πλήθος των κορυφών και των ακμών του δέντρου, άρα $$\mathcal{O}(N)$$ για κάθε ερώτημα. Συνολικά η λύση αυτή έχει -πολυπλοκότητα $$\mathcal{O}(N Q)$$, η οποία μας καλύπτει για τους περιορισμούς -αυτού του υποπροβλήματος. +πολυπλοκότητα $$\mathcal{O}(N \cdot Q)$$, η οποία μας καλύπτει για τους +περιορισμούς αυτού του υποπροβλήματος. -Η πλήρης κώδικας βρίσκεται [εδώ]({% include link_to_source.md +Ο πλήρης κώδικας βρίσκεται [εδώ]({% 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$$ θα βρίσκεται σε κάποιο μικρότερο υποδέντρο. @@ -133,11 +133,11 @@ $$\text{subtree}(L)$$ για το οποίο μπορούμε να υπολογ άλλα φιλοδωρήματα στην πορεία. Ας ορίσουμε λοιπόν την ποσότητα $$\text{supertree\_root\_opt}[u]$$ ως το μέγιστο κέρδος που μπορούμε να έχουμε όταν ξεκινάμε από το σπίτι $$u$$ (χωρίς όμως να συλλέγουμε -το φιλοδώρημα του $$u$$), καταλλήγουμε τελικά στη ρίζα $$R$$ του δέντρου, και +το φιλοδώρημα του $$u$$), καταλήγουμε τελικά στη ρίζα $$R$$ του δέντρου, και σε όλη τη διαδρομή απαγορεύεται να επισκεφτούμε οποιοδήποτε σπίτι βρίσκεται στο υποδέντρο $$\text{subtree}(u)$$. -Εαν υπολογίσουμε την τιμή της παραπάνω ποσότητας για την κορυφή $$L$$, τότε +Εάν υπολογίσουμε την τιμή της παραπάνω ποσότητας για την κορυφή $$L$$, τότε μπορούμε να υπολογίσουμε την απάντηση του ερωτήματος απλά με το άθροισμα: $$ \text{subtree\_loop\_opt}[L] + \text{supertree\_root\_opt}[L]. $$ @@ -180,7 +180,7 @@ $$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ τ τον υπολογισμό των $$\text{supertree\_root\_opt}$$), οι οποίες γίνονται σε $$\mathcal{O}(N)$$ χρόνο. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα και γι' αυτό το υποπρόβλημα. -Μπορείτε να δείτε ολόκληρο των κώδικα [εδώ]({% include link_to_source.md +Μπορείτε να δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md solution_name='subtask3.cc' %}). ## Υποπρόβλημα 4 ($$L = R$$) --- Λύση $$\mathcal{O}(N + Q)$$ @@ -189,7 +189,7 @@ solution_name='subtask3.cc' %}). είναι αρκετά μεγαλύτερο από το προηγούμενο και έτσι η λύση που περιγράψαμε νωρίτερα για το υποπρόβλημα 2 δεν επαρκεί. Θα πρέπει να βρούμε κάποιον τρόπο να απαντάμε τα ερωτήματα χωρίς να διαπερνάμε όλο το δέντρο από την αρχή κάθε φορά. -Ο λόγος που χρειαζόταν να διαπεράσουμε το δέντρο από την αρχή σε κάθε ερώτημα, +Ο λόγος που χρειαζόταν να διασχίσουμε το δέντρο από την αρχή σε κάθε ερώτημα, ήταν ότι έπρεπε να αλλάξουμε ρίζα, κι έτσι οι τιμές που είχαμε για το $$\text{subtree\_loop\_opt}$$ δεν θα ήταν έγκυρες για το δέντρο του επόμενου ερωτήματος. Παρ' όλα αυτά, ας προσπαθήσουμε να σκεφτούμε αν μπορούμε με κάποιο @@ -224,17 +224,17 @@ $$ Ο προ-υπολογισμός των $$\text{subtree\_loop\_opt}$$ και $$\text{supertree\_loop\_opt}$$ με ρίζα την κορυφή 1 μπορεί να γίνει με δύο -διαπεράσεις του δέντρου σε χρόνο $$\mathcal{O}(N)$$ πριν ξεκινήσουμε να απαντάμε +διασχίσεις του δέντρου σε χρόνο $$\mathcal{O}(N)$$ πριν ξεκινήσουμε να απαντάμε ερωτήματα, κι έπειτα για κάθε ερώτημα, μπορούμε να υπολογίσουμε την απάντηση σε σταθερό χρόνο με τον τύπο: $$ \text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]. $$ Συνολικά η λύση αυτή έχει χρονική πολυπλοκότητα $$\mathcal{O}(N + Q)$$. -Μπορείτε να δείτε ολόκληρο των κώδικα [εδώ]({% include link_to_source.md +Μπορείτε να δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md solution_name='subtask4.cc' %}). -## Υποπρόβλημα 5 ($$L_1 = L_2 = \ldots = L_N$$) --- Λύση $$\mathcal{O}(Ν + Q)$$ +## Υποπρόβλημα 5 ($$L_1 = L_2 = \ldots = L_N$$) --- Λύση $$\mathcal{O}(N + Q)$$ Σε αυτή την περίπτωση η αφετηρία και ο προορισμός μπορεί να είναι διαφορετικά σπίτια, όμως η εκφώνηση μας εξασφαλίζει ότι οι αφετηρίες όλων των ερωτημάτων @@ -258,7 +258,7 @@ solution_name='subtask5.cc' %}). το κέρδος της σε υπο-γραμμικό χρόνο. Το μονοπάτι που συνδέει τις κορυφές $$L$$ και $$R$$ θα έχει την εξής μορφή: -ξεκινώντας από την $$L$$ ανεβαίνουμε (0 ή παραπάνω) επίπεδα μέχρι να φτάσουμε +ξεκινώντας από την $$L$$ ανεβαίνουμε (0 ή περισσότερα) επίπεδα μέχρι να φτάσουμε στον Ελάχιστο Κοινό Πρόγονο (Lowest Common Ancestor) των $$L$$ και $$R$$, τον οποίο θα συμβολίζουμε με $$\text{LCA}(L, R)$$, κι έπειτα κινούμαστε προς χαμηλότερα επίπεδα μέχρι να φτάσουμε στην κορυφή $$R$$. Από κάθε κόμβο αυτού του μονοπατιού, @@ -278,7 +278,7 @@ solution_name='subtask5.cc' %}). $$\text{subtree\_loop\_opt}[R] + \text{supertree\_root\_opt}[R]$$ θα πάρουμε λάθος απάντηση καθώς ο δεύτερος όρος περιέχει το κέρδος της λύσης που ανεβαίνει μέχρι τη ρίζα αντί αυτής που καταλήγει στην κορυφή $$z$$. Αυτό όμως - διορθώνεται εύκολα αν **αφαιρέσουμε** το κερδος που αντιστοιχεί στο κομμάτι + διορθώνεται εύκολα αν **αφαιρέσουμε** το κέρδος που αντιστοιχεί στο κομμάτι της διαδρομής που ξεκινάει από την κορυφή $$z$$ και φτάνει στη ρίζα (το έχουμε ήδη υπολογίσει, είναι το $$\text{supertree\_root\_opt}(z)$$) και αντ' αυτού **προσθέσουμε** το κέρδος της _κυκλικής_ διαδρομής που ξεκινάει και @@ -320,7 +320,7 @@ solution_name='subtask5.cc' %}). - (e) το κόστος των δρόμων $$(u, z)$$ και $$(v, z)$$. - Συμπερασματικά, ο τύπος για τον υπολογισμός της απάντησης του ερωτήματος + Συμπερασματικά, ο τύπος για τον υπολογισμό της απάντησης του ερωτήματος $$L, R$$ για την περίπτωση $$LCA(L, R) \neq L, R$$ είναι: $$ @@ -344,7 +344,7 @@ solution_name='subtask5.cc' %}). τις κορυφές $$u, v$$ όπως τις ορίσαμε παραπάνω. Για να το κάνουμε αυτό αποδοτικά, μπορούμε να χρησιμοποιήσουμε μια τεχνική που -λέγεται **Binary Lifting** και η οποία μας επιτρέπει να υπολογίσουμε +λέγεται **binary lifting** και η οποία μας επιτρέπει να υπολογίσουμε τον ελάχιστο κοινό πρόγονο δύο κορυφών σε χρόνο $$\mathcal{O}(\log N)$$, έχοντας κάνει κατάλληλη προεργασία σε χρόνο $$\mathcal{O}(N \log N)$$ μια φορά στην αρχή. Προτείνουμε να ανατρέξετε From 18fd446ab9415c283e8407079da2edb2b9ad4111 Mon Sep 17 00:00:00 2001 From: Makis Arsenis Date: Sat, 22 Mar 2025 16:01:37 -0700 Subject: [PATCH 16/16] Rename vectors --- .../code/37-PDP/tiphunting/optimal.cc | 104 +++++++-------- .../code/37-PDP/tiphunting/subtask2.cc | 6 +- .../code/37-PDP/tiphunting/subtask3.cc | 55 ++++---- .../code/37-PDP/tiphunting/subtask4.cc | 48 +++---- .../code/37-PDP/tiphunting/subtask5.cc | 57 +++++---- contests/_37-PDP/b-tiphunting-solution.md | 118 ++++++++++-------- 6 files changed, 198 insertions(+), 190 deletions(-) diff --git a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc index de108cd8..0663e65b 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/optimal.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/optimal.cc @@ -14,7 +14,7 @@ using vvl = vector>; vvll tree; vl tip, depth, parent, parent_weight; vvl pred; -vector subtree_loop_opt, supertree_loop_opt, supertree_root_opt; +vector best_subtree_tour, best_supertree_tour, best_supertree_root_walk; long long positive_part(long long x) { return max(0LL, x); } @@ -34,9 +34,9 @@ void init(int n) { parent_weight.resize(n, 0); pred.resize(kMaxH, vector(n)); - subtree_loop_opt.resize(n); - supertree_loop_opt.resize(n); - supertree_root_opt.resize(n); + best_subtree_tour.resize(n); + best_supertree_tour.resize(n); + best_supertree_root_walk.resize(n); } // Διασχίζει το δέντρο `tree` ξεκινώντας από την κορυφή `u` και υπολογίζει @@ -44,11 +44,10 @@ void init(int n) { // κορυφή `v != u` στο υποδέντρο της `u`. Οι τιμές `depth[u]`, `parent[u]` και // `parent_weight[u]` θα πρέπει να έχουν ήδη υπολογισθεί από τον caller. // -// `depth[u]`: Το βάθος του `u` στο δέντρο, το οποίο ορίζεται ως το πλήθος -// των ακμών στο μονοπάτι από τον `u` προς τη ρίζα. Για παράδειγμα το βάθος -// της ρίζας είναι 0. -// `parent[u]`: Ο γονέας του `u`. -// `parent_weight[u]`: Κόστος του δρόμου που συνδέει τον `u` με τον γονέα του. +// `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; @@ -61,65 +60,65 @@ void compute_auxiliary(int u) { } // Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές -// `subtree_loop_opt` για την κορυφή `u` κι όλους τους απογόνους της. +// `best_subtree_tour` για την κορυφή `u` κι όλους τους απογόνους της. // -// `subtree_loop_opt[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u`. Mε άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει -// τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(long u) { - subtree_loop_opt[u] = tip[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_subtree_loop_opt(v); - subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + compute_best_subtree_tour(v); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); } } // Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές // `subtree_root_opt` για την κορυφή `u` κι όλους τους απογόνους της, -// χρησιμοποιώντας τις τιμές `subtree_loop_opt` που υπολογίσαμε ήδη στην +// χρησιμοποιώντας τις τιμές `best_subtree_tour` που υπολογίσαμε ήδη στην // προηγούμενη διάσχιση. // -// `supertree_root_opt[u]`: Το κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// από την κορυφή `u`, καταλήγει στη ρίζα του δέντρου και -// μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής -// `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(long u) { - supertree_root_opt[u] = 0; +// `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) - supertree_root_opt[u] = - subtree_loop_opt[parent[u]] + supertree_root_opt[parent[u]] - - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) - parent_weight[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_supertree_root_opt(v); + compute_best_supertree_root_walk(v); } // Διασχίζει το δέντρο `tree` και υπολογίζει αναδρομικά τις τιμές -// `subtree_loop_opt` για την κορυφή `u` κι όλους τους απογόνους της, -// χρησιμοποιώντας τις τιμές `subtree_loop_opt` που υπολογίσαμε ήδη στην +// `best_subtree_tour` για την κορυφή `u` κι όλους τους απογόνους της, +// χρησιμοποιώντας τις τιμές `best_subtree_tour` που υπολογίσαμε ήδη στην // προηγούμενη διάσχιση. // -// supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά +// best_supertree_tour[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά // ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που // ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. -void compute_supertree_loop_opt(int u) { - supertree_loop_opt[u] = 0; +void compute_best_supertree_tour(int u) { + best_supertree_tour[u] = 0; // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. if (parent[u] != u) - supertree_loop_opt[u] = - positive_part(subtree_loop_opt[parent[u]] + supertree_loop_opt[parent[u]] - - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) - 2*parent_weight[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_supertree_loop_opt(v); + compute_best_supertree_tour(v); } // Υπολογίζει τον πίνακα `pred` έτσι ώστε για κάθε 0 <= h <= H, 0 <= u < N: @@ -209,9 +208,9 @@ int main() { compute_pred(); - compute_subtree_loop_opt(0); - compute_supertree_loop_opt(0); - compute_supertree_root_opt(0); + 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; @@ -219,7 +218,7 @@ int main() { L--, R--; if (L == R) { - printf("%lli\n", subtree_loop_opt[L] + supertree_loop_opt[L]); + printf("%lli\n", best_subtree_tour[L] + best_supertree_tour[L]); continue; } @@ -230,23 +229,26 @@ int main() { if (u == -1) { // Η κορυφή `L` είναι πρόγονος της `R`. assert(z == L); - sol = supertree_root_opt[R] - supertree_root_opt[L] + supertree_loop_opt[L] + subtree_loop_opt[R]; + 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 = supertree_root_opt[L] - supertree_root_opt[R] + supertree_loop_opt[R] + subtree_loop_opt[L]; + 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 = supertree_root_opt[L] - supertree_root_opt[u] + subtree_loop_opt[L] // (a) - + supertree_root_opt[R] - supertree_root_opt[v] + subtree_loop_opt[R] // (b) - + subtree_loop_opt[z] // (c1) - - positive_part(subtree_loop_opt[u] - 2*parent_weight[u]) // (c2) - - positive_part(subtree_loop_opt[v] - 2*parent_weight[v]) // (c3) - + supertree_loop_opt[z] // (d) - - (parent_weight[u] + parent_weight[v]); // (e) + 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); diff --git a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc index fd4d5a6b..882e776a 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask2.cc @@ -17,12 +17,12 @@ long long positive_part(long long x) { return max(0LL, x); } // από την κορυφή `u`, καταλήγει πίσω σε αυτή και παραμένει // εξ' ολοκλήρου μέσα στο υποδέντρο που ορίζει η `u`. Με άλλα λόγια, // η διαδρομή απαγορεύεται να διασχίσει το δρόμο `(u, parent)`. -long long subtree_loop_opt(long u, long 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 = subtree_loop_opt(v, u); + long long s = best_subtree_tour(v, u); sol += positive_part(s - 2*w); } @@ -59,7 +59,7 @@ int main() { L--, R--; assert(L == R); - printf("%lli\n", subtree_loop_opt(L, L)); + 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 index 03c024f3..c7c3a47b 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask3.cc @@ -10,47 +10,46 @@ using vvll = vector>; vvll tree; vl tip; -vector subtree_loop_opt, supertree_root_opt; +vector best_subtree_tour, best_supertree_root_walk; long long positive_part(long long x) { return max(0LL, x); } -// Πρώτη διάσχιση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// Πρώτη διάσχιση η οποία υπολογίζει το `best_subtree_tour` για την κορυφή `u` // κι όλους τους απογόνους της. // -// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει -// τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(long u, long parent) { - subtree_loop_opt[u] = tip[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_subtree_loop_opt(v, u); - subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + compute_best_subtree_tour(v, u); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); } } -// Δεύτερη διάσχιση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή -// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διάσχιση. +// Δεύτερη διάσχιση η οποία υπολογίζει το `best_supertree_root_walk` για την +// κορυφή `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `best_subtree_tour` που υπολογίσαμε ήδη στην πρώτη διάσχιση. // -// supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και -// μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής -// `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(long u, long parent, long w) { - supertree_root_opt[u] = 0; +// 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) - supertree_root_opt[u] = - subtree_loop_opt[parent] + supertree_root_opt[parent] - - positive_part(subtree_loop_opt[u] - 2*w) - w; + 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_supertree_root_opt(v, u, w); + compute_best_supertree_root_walk(v, u, w); } int main() { @@ -77,17 +76,17 @@ int main() { tree[v-1].push_back({u-1, w}); } - subtree_loop_opt.resize(n); - supertree_root_opt.resize(n); + 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_subtree_loop_opt(R, R); - compute_supertree_root_opt(R, R, 0); + compute_best_subtree_tour(R, R); + compute_best_supertree_root_walk(R, R, 0); - printf("%lli\n", subtree_loop_opt[L] + supertree_root_opt[L]); + 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 index e21a3deb..9086cc5a 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask4.cc @@ -10,46 +10,46 @@ using vvll = vector>; vvll tree; vl tip; -vector subtree_loop_opt, supertree_loop_opt; +vector best_subtree_tour, best_supertree_tour; long long positive_part(long long x) { return max(0LL, x); } -// Πρώτη διάσχιση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// Πρώτη διάσχιση η οποία υπολογίζει το `best_subtree_tour` για την κορυφή `u` // κι όλους τους απογόνους της. // -// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει -// τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(long u, long parent) { - subtree_loop_opt[u] = tip[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_subtree_loop_opt(v, u); - subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + compute_best_subtree_tour(v, u); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); } } -// Δεύτερη διάσχιση η οποία υπολογίζει το `supertree_loop_opt` για την κορυφή +// Δεύτερη διάσχιση η οποία υπολογίζει το `best_supertree_tour` για την κορυφή // `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διάσχιση. +// `best_subtree_tour` που υπολογίσαμε ήδη στην πρώτη διάσχιση. // -// supertree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά +// best_supertree_tour[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει αλλά // ΚΑΙ καταλήγει στην κορυφή `u`, και μένει πάντα ΕΚΤΟΣ του υποδέντρου που // ορίζει η `u`. Το φιλοδώρημα της κορυφής `u` ΔΕΝ προσμετράται. -void compute_supertree_loop_opt(long u, long parent, long w) { - supertree_loop_opt[u] = 0; +void compute_best_supertree_tour(long u, long parent, long w) { + best_supertree_tour[u] = 0; // Αν η κορυφή `u` ΔΕΝ είναι ρίζα. if (parent != u) - supertree_loop_opt[u] = - positive_part(subtree_loop_opt[parent] + supertree_loop_opt[parent] - - positive_part(subtree_loop_opt[u] - 2*w) - 2*w); + 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_supertree_loop_opt(v, u, w); + compute_best_supertree_tour(v, u, w); } int main() { @@ -76,11 +76,11 @@ int main() { tree[v-1].push_back({u-1, w}); } - subtree_loop_opt.resize(n); - compute_subtree_loop_opt(0, 0); + best_subtree_tour.resize(n); + compute_best_subtree_tour(0, 0); - supertree_loop_opt.resize(n); - compute_supertree_loop_opt(0, 0, 0); + best_supertree_tour.resize(n); + compute_best_supertree_tour(0, 0, 0); for (long i = 0; i < q; ++i) { long L, R; @@ -88,7 +88,7 @@ int main() { L--, R--; assert(L == R); - printf("%lli\n", subtree_loop_opt[L] + supertree_loop_opt[L]); + 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 index ee19af94..8152850f 100644 --- a/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc +++ b/_includes/source_code/code/37-PDP/tiphunting/subtask5.cc @@ -10,47 +10,46 @@ using vvll = vector>; vvll tree; vl tip; -vector subtree_loop_opt, supertree_root_opt; +vector best_subtree_tour, best_supertree_root_walk; long long positive_part(long long x) { return max(0LL, x); } -// Πρώτη διάσχιση η οποία υπολογίζει το `subtree_loop_opt` για την κορυφή `u` +// Πρώτη διάσχιση η οποία υπολογίζει το `best_subtree_tour` για την κορυφή `u` // κι όλους τους απογόνους της. // -// subtree_loop_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// και καταλήγει πάλι πίσω στο `u`, παραμένοντας στο υποδέντρο που ορίζει -// η κορυφή `u`. Με άλλα λόγια, η διαδρομή απαγορεύεται να διασχίσει -// τον δρόμο `(u, parent)`. -void compute_subtree_loop_opt(long u, long parent) { - subtree_loop_opt[u] = tip[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_subtree_loop_opt(v, u); - subtree_loop_opt[u] += positive_part(subtree_loop_opt[v] - 2*w); + compute_best_subtree_tour(v, u); + best_subtree_tour[u] += positive_part(best_subtree_tour[v] - 2*w); } } -// Δεύτερη διάσχιση η οποία υπολογίζει το `supertree_root_opt` για την κορυφή -// `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές -// `subtree_loop_opt` που υπολογίσαμε ήδη στην πρώτη διάσχιση. +// Δεύτερη διάσχιση η οποία υπολογίζει το `best_supertree_root_walk` για την +// κορυφή `u` κι όλους τους απογόνους της, χρησιμοποιώντας τις τιμές +// `best_subtree_tour` που υπολογίσαμε ήδη στην πρώτη διάσχιση. // -// supertree_root_opt[u] = κέρδος της βέλτιστης διαδρομής η οποία ξεκινάει -// από την κορυφή `u`, καταλήγει στη ρίζα τους δέντρου και -// μένει πάντα ΕΚΤΟΣ του υποδέντρου που ορίζει η `u`. Το φιλοδώρημα της κορυφής -// `u` ΔΕΝ προσμετράται. -void compute_supertree_root_opt(long u, long parent, long w) { - supertree_root_opt[u] = 0; +// 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) - supertree_root_opt[u] = - subtree_loop_opt[parent] + supertree_root_opt[parent] - - positive_part(subtree_loop_opt[u] - 2*w) - w; + 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_supertree_root_opt(v, u, w); + compute_best_supertree_root_walk(v, u, w); } int main() { @@ -81,14 +80,14 @@ int main() { scanf("%li%li", &L, &R); L--, R--; - subtree_loop_opt.resize(n); - compute_subtree_loop_opt(L, L);; + best_subtree_tour.resize(n); + compute_best_subtree_tour(L, L);; - supertree_root_opt.resize(n); - compute_supertree_root_opt(L, L, 0); + best_supertree_root_walk.resize(n); + compute_best_supertree_root_walk(L, L, 0); // Απάντηση στο πρώτο ερώτημα. - printf("%lli\n", subtree_loop_opt[R] + supertree_root_opt[R]); + printf("%lli\n", best_subtree_tour[R] + best_supertree_root_walk[R]); // Απάντηση στα υπόλοιπα `q-1` ερωτήματα. for (long i = 1; i < q; ++i) { @@ -97,7 +96,7 @@ int main() { assert(L == new_L - 1); R--; - printf("%lli\n", subtree_loop_opt[R] + supertree_root_opt[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 index dd7123d5..4ffd46f0 100644 --- a/contests/_37-PDP/b-tiphunting-solution.md +++ b/contests/_37-PDP/b-tiphunting-solution.md @@ -84,28 +84,28 @@ $$\text{subtree}(u)$$. Για κάθε ένα από αυτά τα δέντρα 2. να _μη διασχίσουμε ποτέ_ το δρόμο που συνδέει το σπίτι $$L$$ με το σπίτι $$u$$. -Ας ορίσουμε $$\text{subtree\_loop\_opt}[u]$$ ως το μέγιστό δυνατό κέρδος +Ας ορίσουμε $$\text{best\_subtree\_tour}[u]$$ ως το μέγιστό δυνατό κέρδος που μπορούμε να έχουμε αν ξεκινήσουμε από το σπίτι $$u$$, κινούμαστε μόνο μέσα στο υποδέντρο που ορίζει το σπίτι $$u$$, και τελικά επιστρέψουμε πάλι στο $$u$$. Είναι φανερό ότι θα ακολουθήσουμε την επιλογή 1 όταν αυτή παράγει κέρδος, -δηλαδή όταν $$\text{subtree\_loop\_opt}[u] - 2\cdot w \ge 0$$. +δηλαδή όταν $$\text{best\_subtree\_tour}[u] - 2\cdot w \ge 0$$. Διαφορετικά η επιλογή 2 είναι καλύτερη --- ή ισοδύναμη σε περίπτωση μηδενικού κέρδους. -Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{subtree\_loop\_opt}$$ +Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{best\_subtree\_tour}$$ εφαρμόζοντας τον παρακάτω αναδρομικό τύπο: -$$ \text{subtree\_loop\_opt}[u] = t_u + \sum_{v \in \text{children}(u)} -\left( \text{subtree\_loop\_opt}[v] - 2 \cdot w_{u, v} \right)^+ $$ +$$ \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{subtree\_loop\_opt}[L]$$. +Η τελική απάντηση στο ερώτημα βρίσκεται στο $$\text{best\_subtree\_tour}[L]$$. Για τον υπολογισμό όλων των παραπάνω τιμών, χρειάζεται για κάθε κορυφή να υπολογίσουμε ένα άθροισμα που περιλαμβάνει όλα τα παιδιά της, συνεπώς @@ -125,13 +125,13 @@ solution_name='subtask2.cc' %}) ρίζα του δέντρου. Η αφετηρία $$L$$ θα βρίσκεται σε κάποιο μικρότερο υποδέντρο. Ξεκινώντας από την $$L$$, είμαστε ελεύθεροι να εξερευνήσουμε το υποδέντρο $$\text{subtree}(L)$$ για το οποίο μπορούμε να υπολογίσουμε τη βέλτιστη -διαδρομή χρησιμοποιώντας το $$\text{subtree\_loop\_opt}[L]$$ που ορίσαμε +διαδρομή χρησιμοποιώντας το $$\text{best\_subtree\_tour}[L]$$ που ορίσαμε προηγουμένως. Σε αντίθεση όμως με πριν, μόλις επιστρέψουμε στο $$L$$ θα πρέπει να ανέβουμε ένα επίπεδο παραπάνω, ακολουθώντας το μονοπάτι προς τη ρίζα και μαζεύοντας κι άλλα φιλοδωρήματα στην πορεία. Ας ορίσουμε λοιπόν την ποσότητα -$$\text{supertree\_root\_opt}[u]$$ ως το μέγιστο κέρδος που +$$\text{best\_supertree\_root\_walk}[u]$$ ως το μέγιστο κέρδος που μπορούμε να έχουμε όταν ξεκινάμε από το σπίτι $$u$$ (χωρίς όμως να συλλέγουμε το φιλοδώρημα του $$u$$), καταλήγουμε τελικά στη ρίζα $$R$$ του δέντρου, και σε όλη τη διαδρομή απαγορεύεται να επισκεφτούμε οποιοδήποτε σπίτι βρίσκεται στο @@ -140,31 +140,31 @@ $$\text{supertree\_root\_opt}[u]$$ ως το μέγιστο κέρδος που Εάν υπολογίσουμε την τιμή της παραπάνω ποσότητας για την κορυφή $$L$$, τότε μπορούμε να υπολογίσουμε την απάντηση του ερωτήματος απλά με το άθροισμα: -$$ \text{subtree\_loop\_opt}[L] + \text{supertree\_root\_opt}[L]. $$ +$$ \text{best\_subtree\_tour}[L] + \text{best\_supertree\_root\_walk}[L]. $$ -Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{supertree\_root\_opt}[u]$$ από -"πάνω προς τα κάτω" χρησιμοποιώντας τις τιμές $$\text{subtree\_loop\_opt}$$ ως -εξής: αν η κορυφή $$u$$ είναι η ρίζα, τότε εξ ορισμού -$$\text{supertree\_root\_opt}[u] = 0$$, διαφορετικά, +Μπορούμε να υπολογίσουμε όλες τις τιμές $$\text{best\_supertree\_root\_walk}[u]$$ +από "πάνω προς τα κάτω" χρησιμοποιώντας τις τιμές $$\text{best\_subtree\_tour}$$ +ως εξής: αν η κορυφή $$u$$ είναι η ρίζα, τότε εξ +ορισμού $$\text{best\_supertree\_root\_walk}[u] = 0$$, διαφορετικά, $$ \begin{align*} - \text{supertree\_root\_opt}[u] =& - \text{supertree\_root\_opt}[\text{parent}(u)] - + \text{subtree\_loop\_opt}[\text{parent}(u)] \\ - &- (\text{subtree\_loop\_opt}[u] - 2 \cdot w_{u, \text{parent}(u)})^+ + \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{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ του $$u$$, +$$\text{best\_supertree\_root\_walk}$$ για τον γονέα $$\text{parent(u)}$$ του $$u$$, που μπορούμε να θεωρήσουμε ότι έχουμε ήδη υπολογίσει (θυμηθείτε ότι υπολογίζουμε τις τιμές από πάνω προς τα κάτω), η οποία περιλαμβάνει τη βέλτιστη λύση που αφορά τη διαδρομή από $$\text{parent}(u)$$ μέχρι τη ρίζα. Το μόνο που μένει είναι να συμπεριλάβουμε το κομμάτι της διαδρομής που εξερευνά τα σπίτια που συνδέονται στο $$u$$ και στον γονέα του. Η τιμή για αυτό το κομμάτι της -διαδρομής είναι _σχεδόν_ η τιμή $$\text{subtree\_loop\_opt}[\text{parent}(u)]$$ +διαδρομής είναι _σχεδόν_ η τιμή $$\text{best\_subtree\_tour}[\text{parent}(u)]$$ που είχαμε ορίσει προηγουμένως, με τη διαφορά ότι αυτή η τιμή πιθανώς να περιλαμβάνει το κόστος της ακμής $$(u, \text{parent}[u])$$ _δύο φορές_. Πρέπει λοιπόν να διορθώσουμε την τιμή, αφαιρώντας την κατάλληλη ποσότητα, μόνο όμως @@ -173,11 +173,11 @@ $$\text{supertree\_root\_opt}$$ για τον γονέα $$\text{parent(u)}$$ τ Μια αναδρομική υλοποίηση είναι η παρακάτω: -{% include code.md solution_name='subtask3.cc' start=17 end=52 %} +{% include code.md solution_name='subtask3.cc' start=17 end=53 %} Για την επίλυση ενός ερωτήματος λοιπόν χρειάστηκαν _δύο διασχίσεις_ του δέντρου -(μια για τον υπολογισμό των $$\text{subtree\_loop\_opt}$$, και έπειτα μια για -τον υπολογισμό των $$\text{supertree\_root\_opt}$$), οι οποίες γίνονται σε +(μια για τον υπολογισμό των $$\text{best\_subtree\_tour}$$, και έπειτα μια για +τον υπολογισμό των $$\text{best\_supertree\_root\_walk}$$), οι οποίες γίνονται σε $$\mathcal{O}(N)$$ χρόνο. Συνολικά $$\mathcal{O}(N \cdot Q)$$ πολυπλοκότητα και γι' αυτό το υποπρόβλημα. Μπορείτε να δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md @@ -191,21 +191,21 @@ solution_name='subtask3.cc' %}). απαντάμε τα ερωτήματα χωρίς να διαπερνάμε όλο το δέντρο από την αρχή κάθε φορά. Ο λόγος που χρειαζόταν να διασχίσουμε το δέντρο από την αρχή σε κάθε ερώτημα, ήταν ότι έπρεπε να αλλάξουμε ρίζα, κι έτσι οι τιμές που είχαμε για το -$$\text{subtree\_loop\_opt}$$ δεν θα ήταν έγκυρες για το δέντρο του επόμενου -ερωτήματος. Παρ' όλα αυτά, ας προσπαθήσουμε να σκεφτούμε αν μπορούμε με κάποιο -τρόπο να τις εκμεταλλευτούμε ώστε να απαντήσουμε μελλοντικά ερωτήματα. +$$\text{best\_subtree\_tour}$$ δεν θα ήταν έγκυρες για το δέντρο του +επόμενου ερωτήματος. Παρ' όλα αυτά, ας προσπαθήσουμε να σκεφτούμε αν μπορούμε +με κάποιο τρόπο να τις εκμεταλλευτούμε ώστε να απαντήσουμε μελλοντικά ερωτήματα. Ας θεωρήσουμε λοιπόν στο εξής ότι η ρίζα του δέντρου είναι πάντα η κορυφή 1, κι -ας υπολογίσουμε τις τιμές $$\text{subtree\_loop\_opt}$$ όπως τις ορίσαμε +ας υπολογίσουμε τις τιμές $$\text{best\_subtree\_tour}$$ όπως τις ορίσαμε παραπάνω. Η απάντηση σε ένα ερώτημα στο οποίο $$L = R \ne 1$$, είναι _σχεδόν_ -ίση με $$\text{subtree\_loop\_opt}[L]$$, με τη διαφορά ότι αυτή η τιμή δεν +ίση με $$\text{best\_subtree\_tour}[L]$$, με τη διαφορά ότι αυτή η τιμή δεν περιλαμβάνει διαδρομές που ανεβαίνουν σε υψηλότερα επίπεδα του δέντρου και γυρνάνε πάλι πίσω στην κορυφή $$L$$. Αυτό όμως διορθώνεται εύκολα αν προ-υπολογίσουμε για κάθε κορυφή $$u$$ αυτή την τιμή, ας την ονομάσουμε -$$\text{supertree\_loop\_opt}[u]$$, ώστε να την έχουμε διαθέσιμη όταν τη -χρειαστούμε για να απαντήσουμε κάποιο ερώτημα. +$$\text{best\_\textcolor{red}{supertree}\_tour}[u]$$, ώστε να την έχουμε +διαθέσιμη όταν τη χρειαστούμε για να απαντήσουμε κάποιο ερώτημα. -Μάλιστα, οι τιμές αυτές μοιάζουν πολύ με τις τιμές $$\text{supertree\_root\_opt}$$ που +Μάλιστα, οι τιμές αυτές μοιάζουν πολύ με τις τιμές $$\text{best\_supertree\_root\_walk}$$ που ορίσαμε στο προηγούμενο υποπρόβλημα, με τη διαφορά όμως ότι δεν είναι πλέον αναγκαίο να φτάσουμε μέχρι τη ρίζα, αλλά πρέπει να γυρίσουμε πίσω στην κορυφή $$u$$, το οποίο σημαίνει ότι πρέπει να μετρήσουμε το κόστος του δρόμου @@ -214,21 +214,21 @@ $$(u, \text{parent}(u))$$ **δύο φορές**. Ο τύπος που είχαμ $$ \begin{align*} -\text{supertree\_loop\_opt}[u] = - ( & \text{supertree\_loop\_opt}[\text{parent}(u)] - + \text{subtree\_loop\_opt}[\text{parent}(u)] \\ - &- (\text{subtree\_loop\_opt}[u] - 2 \cdot w_{u, \text{parent}(u)})^+ +\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{subtree\_loop\_opt}$$ και -$$\text{supertree\_loop\_opt}$$ με ρίζα την κορυφή 1 μπορεί να γίνει με δύο +Ο προ-υπολογισμός των $$\text{best\_subtree\_tour}$$ και +$$\text{best\_supertree\_tour}$$ με ρίζα την κορυφή 1 μπορεί να γίνει με δύο διασχίσεις του δέντρου σε χρόνο $$\mathcal{O}(N)$$ πριν ξεκινήσουμε να απαντάμε ερωτήματα, κι έπειτα για κάθε ερώτημα, μπορούμε να υπολογίσουμε την απάντηση σε σταθερό χρόνο με τον τύπο: -$$ \text{subtree\_loop\_opt}[L] + \text{supertree\_loop\_opt}[L]. $$ +$$ \text{best\_subtree\_tour}[L] + \text{best\_supertree\_tour}[L]. $$ Συνολικά η λύση αυτή έχει χρονική πολυπλοκότητα $$\mathcal{O}(N + Q)$$. Μπορείτε να δείτε ολόκληρο τον κώδικα [εδώ]({% include link_to_source.md @@ -240,7 +240,7 @@ solution_name='subtask4.cc' %}). σπίτια, όμως η εκφώνηση μας εξασφαλίζει ότι οι αφετηρίες όλων των ερωτημάτων θα είναι οι ίδιες. Αυτό μας επιτρέπει να θέσουμε την κοινή κορυφή $$L$$ ως ρίζα και να εφαρμόσουμε τη λύση του υποπροβλήματος 3, υπολογίζοντας όμως -τις βοηθητικές τιμές $$\text{subtree\_loop\_opt}$$ και $$\text{supertree\_root\_opt}$$ +τις βοηθητικές τιμές $$\text{best\_subtree\_tour}$$ και $$\text{best\_supertree\_root\_walk}$$ μόνο μια φορά στην αρχή, και απαντώντας μετά το κάθε ερώτημα σε σταθερό χρόνο. Ο πλήρης κώδικας βρίσκεται [εδώ]({% include link_to_source.md solution_name='subtask5.cc' %}). @@ -263,10 +263,10 @@ solution_name='subtask5.cc' %}). τον οποίο θα συμβολίζουμε με $$\text{LCA}(L, R)$$, κι έπειτα κινούμαστε προς χαμηλότερα επίπεδα μέχρι να φτάσουμε στην κορυφή $$R$$. Από κάθε κόμβο αυτού του μονοπατιού, μπορούμε να εξερευνήσουμε υποδέντρα ώστε να μαζέψουμε φιλοδωρήματα (ακριβώς όπως -στον ορισμό του $$\text{subtree\_loop\_opt}$$). Επιπλέον, από +στον ορισμό του $$\text{best\_subtree\_tour}$$). Επιπλέον, από την κορυφή $$\text{LCA}(L, R)$$ έχουμε τη δυνατότητα να ακολουθήσουμε μια κυκλική διαδρομή που επισκέπτεται υψηλότερα επίπεδα του δέντρου (ακριβώς -όπως στον ορισμό του $$\text{supertree\_loop\_opt}$$ παραπάνω). +όπως στον ορισμό του $$\text{best\_supertree\_tour}$$ παραπάνω). Για την ώρα, ας υποθέσουμε ότι γνωρίζουμε τον ελάχιστο κοινό πρόγονο $$z = \text{LCA}(L, R)$$. @@ -275,25 +275,33 @@ solution_name='subtask5.cc' %}). - $$z = L$$: Η περίπτωση αυτή μοιάζει πολύ με το υποπρόβλημα 5, με τη διαφορά ότι αφετηρία δεν είναι η ρίζα του δέντρου και συνεπώς αν εφαρμόσουμε τον τύπο - $$\text{subtree\_loop\_opt}[R] + \text{supertree\_root\_opt}[R]$$ θα πάρουμε + $$\text{best\_subtree\_tour}[R] + \text{best\_supertree\_root\_walk}[R]$$ θα πάρουμε λάθος απάντηση καθώς ο δεύτερος όρος περιέχει το κέρδος της λύσης που ανεβαίνει μέχρι τη ρίζα αντί αυτής που καταλήγει στην κορυφή $$z$$. Αυτό όμως διορθώνεται εύκολα αν **αφαιρέσουμε** το κέρδος που αντιστοιχεί στο κομμάτι της διαδρομής που ξεκινάει από την κορυφή $$z$$ και φτάνει στη ρίζα (το - έχουμε ήδη υπολογίσει, είναι το $$\text{supertree\_root\_opt}(z)$$) και αντ' + έχουμε ήδη υπολογίσει, είναι το $$\text{best\_supertree\_root\_walk}(z)$$) και αντ' αυτού **προσθέσουμε** το κέρδος της _κυκλικής_ διαδρομής που ξεκινάει και επιστρέφει στο $$z$$, μαζεύοντας φιλοδωρήματα από κόμβους εκτός του $$\text{subtree}(z)$$ (κι αυτή η τιμή είναι διαθέσιμη στο - $$\text{supertree\_loop\_opt}(z)$$). Συνοψίζοντας, η + $$\text{best\_supertree\_tour}(z)$$). Συνοψίζοντας, η απάντηση σε αυτή την περίπτωση είναι: - $$ \text{supertree\_root\_opt}(L) + \text{subtree\_loop\_opt}(R) - -\text{supertree\_root\_opt}(z) + \text{supertree\_loop\_opt}(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$$: Αντίστοιχα με την προηγούμενη περίπτωση, η απάντηση είναι: - $$ \text{supertree\_root\_opt}(R) + \text{subtree\_loop\_opt}(L) - -\text{supertree\_root\_opt}(z) + \text{supertree\_loop\_opt}(z) $$ + $$ + \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$$, το άθροισμα των @@ -325,16 +333,16 @@ solution_name='subtask5.cc' %}). $$ \begin{align*} - &\underbrace{(\text{supertree\_root\_opt}[L] - - \text{supertree\_root\_opt}[u] - \text{subtree\_loop\_opt}[L])}_{(a)} \\ - +&\underbrace{(\text{supertree\_root\_opt}[R] - - \text{supertree\_root\_opt}[v] - \text{subtree\_loop\_opt}[R])}_{(b)} \\ + &\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{subtree\_loop\_opt}[z] - - (\text{subtree\_loop\_opt}[u] - 2\cdot w_{u, z})^+ - - (\text{subtree\_loop\_opt}[v] - 2\cdot w_{v, z})^+) + \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{supertree\_loop\_opt}[z]}_{(d)} \\ + +&\underbrace{\text{best\_supertree\_tour}[z]}_{(d)} \\ -&\underbrace{(w_{u, z} + w_{v, z})}_{(e)} \end{align*} $$