Submission #1309571

#TimeUsernameProblemLanguageResultExecution timeMemory
1309571ayuxhkumxr22Travelling Trader (CCO23_day2problem2)C++20
0 / 25
8 ms1088 KiB
/** * Solution for CCO 2023 Day 2 Problem 2: Travelling Trader * * Logic: * This is a Tree DP problem where we must select a subset of nodes and a traversal order. * Constraint: Distance between consecutive profit-takes <= K. * * DP State: dp[u][d_in] * d_in: The distance from the last selected ancestor. * Returns: A list of Pareto-optimal results {profit, d_out}. * d_out: The distance from the last selected node in this subtree to u's parent. * * Strategies at node u: * 1. Pick u FIRST: Reset gap to 0 immediately. Then visit children. * - Good for: Maximizing profit within the subtree (provides gap 1 to children). * 2. Pick u LAST: Visit children first (passing d_in), then pick u. * - Good for: Returning a small gap (1) to the parent. * 3. SKIP u: Visit children passing d_in. * - Good for: When picking u is impossible or suboptimal (rare). * * Greedy Chain Logic: * To merge children results, we use a greedy strategy: * Always visit the child that allows continuation (valid input gap) and returns the * SMALLEST output gap. This keeps the "gap budget" loose for subsequent children. */ #include <iostream> #include <vector> #include <algorithm> using namespace std; // --- Constants & Globals --- const int MAXN = 200005; struct State { int d_out; long long profit; }; // Result for a child's greedy consideration struct ChildMove { int next_req_in; // The input gap this child needs int ret_out; // The output gap this child returns long long profit; int child_idx; // Original index }; int N, K; vector<int> adj[MAXN]; vector<int> tree[MAXN]; long long P[MAXN]; // dp[u][d_in] -> List of {d_out, profit} // d_in ranges 1..K (since d_in=0 is impossible for a child, parent dist >= 1) // We map 0-based index to 1..K vector<State> dp[MAXN][4]; // --- Helper Functions --- void build_tree(int u, int p) { for (int v : adj[u]) { if (v != p) { tree[u].push_back(v); build_tree(v, u); } } } // Keep only pareto optimal states: Max profit for each d_out void optimize_states(vector<State>& states) { if (states.empty()) return; // We want to map each d_out (1..K) to max profit. // Since K is small, just use a fixed array. long long best_p[5]; fill(best_p, best_p + 5, -1); for (auto& s : states) { if (s.d_out <= K+1) { // d_out can be K+1 (invalid/end), but we filter later // For strict pareto: if we have d_out=1, profit=10, // and d_out=2, profit=10, the d_out=2 is useless. // But simpler: just map best profit per d_out. if (s.profit > best_p[s.d_out]) best_p[s.d_out] = s.profit; } } states.clear(); // Reconstruct pareto frontier long long max_so_far = -1; // Iterate from largest gap to smallest. // If smaller gap doesn't have more profit, it's useless? // No, smaller gap is structurally better. // If larger gap has MORE profit, we keep it. // If smaller gap has LESS profit, we keep it (structural advantage). for (int d = 1; d <= K + 1; d++) { if (best_p[d] != -1) { states.push_back({d, best_p[d]}); } } } // Execute Greedy Chain Strategy // start_gap: Gap before visiting first child // force_u_last: If true, we append u at the end // u_picked_first: If true, we already took profit P[u], start_gap should be 0. State solve_chain(int u, int start_gap, bool force_u_last, bool u_picked_first) { long long current_profit = (u_picked_first ? P[u] : 0); int current_gap = start_gap; // If we skip u and start_gap >= K, we can't do anything (input to child must be <= K) // Child input = current_gap + 1. So we need current_gap < K. if (!u_picked_first && !force_u_last && start_gap >= K) return {start_gap + 1, -1}; // Invalid // Collect best moves from all children // Since greedy selection depends on current_gap, and current_gap changes, // we normally re-evaluate. BUT K is small. // We can pre-calculate the best move each child offers for input 1, 2, 3... // Then dynamically pick. // Store child options: options[v][input_gap] = {ret_gap, profit} // Optimization: Since we simply want "Smallest Return Gap", we can sort children. // However, a child might accept input 1 but not 2. // Let's use a "used" mask. vector<bool> used(tree[u].size(), false); while (true) { int req_in = current_gap + 1; if (req_in > K) break; // Cannot enter any child int best_child = -1; int best_ret = 100; long long best_gain = -1; // Scan children for (int i = 0; i < tree[u].size(); i++) { if (used[i]) continue; int v = tree[u][i]; // Look up dp[v][req_in] // We want the option with Min d_out, then Max profit for (auto& s : dp[v][req_in]) { // Return from child is s.d_out. // New gap at u will be s.d_out + 1. // We want s.d_out to be small. if (s.d_out < best_ret) { best_ret = s.d_out; best_gain = s.profit; best_child = i; } else if (s.d_out == best_ret) { if (s.profit > best_gain) { best_gain = s.profit; best_child = i; } } } } if (best_child != -1) { used[best_child] = true; current_profit += best_gain; current_gap = best_ret; // gap at u becomes child's return // Wait, gap at u becomes child_out + 1? // DP state stores d_out relative to PARENT. // s.d_out is distance from Child's profit to Child's Parent (u). // So s.d_out is exactly the gap at u upon return. // Correct. } else { break; } } if (force_u_last) { // We try to append u // Valid if current_gap + 1 <= K? // No, "gap" is distance from last profit. // We are at u. Distance is current_gap. // If we pick u, distance becomes 0. // Condition: current_gap <= K. (Actually distance from last profit). // Since we enforced req_in <= K for children steps, current_gap is always valid dist? // Yes, current_gap is exactly dist to last profit. if (current_gap <= K) { current_profit += P[u]; current_gap = 0; } else { return {100, -1}; // Fail } } // Final d_out relative to u's parent is current_gap + 1 return {current_gap + 1, current_profit}; } void solve(int u) { // Post-order DFS for (int v : tree[u]) solve(v); // Calculate for all incoming gaps for (int d_in = 1; d_in <= K; d_in++) { // Strategy 1: Pick First // Start gap 0. State s1 = solve_chain(u, 0, false, true); if (s1.profit != -1) dp[u][d_in].push_back(s1); // Strategy 2: Pick Last // Start gap d_in. State s2 = solve_chain(u, d_in, true, false); if (s2.profit != -1) dp[u][d_in].push_back(s2); // Strategy 3: Skip // Start gap d_in. State s3 = solve_chain(u, d_in, false, false); if (s3.profit != -1) dp[u][d_in].push_back(s3); optimize_states(dp[u][d_in]); } } // Global Path Reconstruction vector<int> final_path; void reconstruct(int u, int start_gap, int strategy) { // 1: Pick First, 2: Pick Last, 3: Skip bool u_picked_first = (strategy == 1); bool force_u_last = (strategy == 2); if (u_picked_first) final_path.push_back(u); int current_gap = (u_picked_first ? 0 : start_gap); vector<bool> used(tree[u].size(), false); // Re-run greedy loop to find child order while (true) { int req_in = current_gap + 1; if (req_in > K) break; int best_child = -1; int best_ret = 100; long long best_gain = -1; // Need to identify WHICH state of child was used? // We assumed we picked the optimal one. // We need to match the specific outcome. // To simplify: Just look for the move that is "optimal" locally. // This works because the DP value was built using this exact greedy logic. for (int i = 0; i < tree[u].size(); i++) { if (used[i]) continue; int v = tree[u][i]; for (auto& s : dp[v][req_in]) { if (s.d_out < best_ret) { best_ret = s.d_out; best_gain = s.profit; best_child = i; } else if (s.d_out == best_ret) { if (s.profit > best_gain) { best_gain = s.profit; best_child = i; } } } } if (best_child != -1) { used[best_child] = true; // Recurse into child int v = tree[u][best_child]; // Determine Child Strategy // We know child received req_in and produced best_gain/best_ret. // We check which strategy at child yields this. // Order preference: Pick First, Pick Last, Skip. // (Must match DP build order or logic). int child_strat = 0; // Check Strat 1 bool found = false; // We need to re-call solve_chain or just peek DP? // DP stores merged results. We need to check hypothetical. // Since we don't store strategy source in DP, we check by re-solving. // Check Pick First (1) State c1 = solve_chain(v, 0, false, true); if (c1.d_out == best_ret + 1 && c1.profit == best_gain) child_strat = 1; else { // Check Pick Last (2) State c2 = solve_chain(v, req_in, true, false); if (c2.d_out == best_ret + 1 && c2.profit == best_gain) child_strat = 2; else child_strat = 3; // Skip } reconstruct(v, req_in, child_strat); current_gap = best_ret; } else { break; } } if (force_u_last) final_path.push_back(u); } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); if (!(cin >> N >> K)) return 0; for (int i = 0; i < N - 1; i++) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } for (int i = 1; i <= N; i++) cin >> P[i]; build_tree(1, 0); solve(1); // Root (1) must be picked. // It is equivalent to "Pick First" with dummy input. // Or just check which strategy at root yields max profit. // Since root is picked, only Strat 1 or 2 possible. long long ans = -1; int best_strat = 1; // Check Strat 1 (Pick First) State s1 = solve_chain(1, 0, false, true); if (s1.profit > ans) { ans = s1.profit; best_strat = 1; } // Check Strat 2 (Pick Last) -- Only valid if input valid? // Root input is conceptually 0? No, root has no parent. // But "Pick Last" implies we traverse children BEFORE root. // Children would need input gap. // If we assume trader starts at 1, he is AT 1. // He can take profit immediately (Pick First). // Can he delay 1? "Trader starts by doing business in city 1". // Problem says: "starts by doing business in city 1". // So Strategy 1 (Pick First) is MANDATORY for root. ans = s1.profit; best_strat = 1; cout << ans << endl; reconstruct(1, 0, best_strat); cout << final_path.size() << endl; for (int i = 0; i < final_path.size(); i++) { cout << final_path[i] << (i == final_path.size() - 1 ? "" : " "); } cout << endl; return 0; }
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...