Submission #1309567

#TimeUsernameProblemLanguageResultExecution timeMemory
1309567ayuxhkumxr22Travelling Trader (CCO23_day2problem2)C++20
0 / 25
7 ms1092 KiB
#include <iostream> #include <vector> #include <algorithm> #include <map> using namespace std; // --- Constants & Globals --- const int MAXN = 200005; const long long INF = 1e18; int N, K; vector<int> adj[MAXN]; vector<int> tree[MAXN]; // Directed tree structure after DFS long long P[MAXN]; // Profit of city i // DP State: dp[u][d_in] stores a list of possible outcomes {d_out, profit} // d_in: distance from the nearest selected ancestor (0..K) // d_out: distance to the nearest selected descendant (0..K) struct State { int d_out; long long profit; }; // Memoization table // d_in can range from 0 to K. We use size 4 since K <= 3. vector<State> dp[MAXN][4]; bool visited[MAXN][4]; // --- Helper Functions --- // Filter Pareto optimal states to keep the list small // We want small d_out and large profit. // If state A has (d_out <= B.d_out) and (profit >= B.profit), A dominates B. void optimize_states(vector<State>& states) { if (states.empty()) return; // Sort by d_out ascending, then profit descending sort(states.begin(), states.end(), [](const State& a, const State& b) { if (a.d_out != b.d_out) return a.d_out < b.d_out; return a.profit > b.profit; }); vector<State> optimal; long long max_p = -1; for (const auto& s : states) { if (s.profit > max_p) { optimal.push_back(s); max_p = s.profit; } } states = optimal; } // Pre-processing to build tree direction void build_tree(int u, int p) { for (int v : adj[u]) { if (v != p) { tree[u].push_back(v); build_tree(v, u); } } } // --- Core DP Logic --- // Returns the best state (max profit) found by the Greedy Merge strategy // picking_u: boolean, true if we select current node u // d_in_start: the initial gap. If picking_u is true, this is effectively 0. State get_greedy_outcome(int u, int d_in_start, bool picking_u) { long long current_profit = (picking_u ? P[u] : 0); int current_gap = (picking_u ? 0 : d_in_start); // If the starting gap is already invalid, return invalid state // Note: If picking_u is true, current_gap is 0, always valid. // If skipping, we must check. if (current_gap >= K && !picking_u) return {K + 1, -1}; // We keep track of which children are used to avoid cycles in our greedy selection vector<bool> child_used(tree[u].size(), false); // Greedy Simulation: // Repeatedly try to visit a child that accepts (current_gap + 1). // Among candidates, pick the one that returns the smallest d_out (to keep the path alive). // Tie-break with max profit. while (true) { int req_in = current_gap + 1; if (req_in > K) break; // Cannot extend further int best_child_idx = -1; int best_state_idx = -1; int best_next_gap = 100; // Minimize this long long best_gain = -1; // Maximize this // Scan all children to find the best candidate for (int i = 0; i < tree[u].size(); i++) { if (child_used[i]) continue; int v = tree[u][i]; // Look at the child's DP results for input req_in // We assume dp[v] is already computed for (int s_i = 0; s_i < dp[v][req_in].size(); s_i++) { State& s = dp[v][req_in][s_i]; // If we visit this child, the new gap at u becomes (child's d_out + 1) int next_g = s.d_out + 1; // Priority: Lower Gap > Higher Profit if (next_g < best_next_gap) { best_next_gap = next_g; best_gain = s.profit; best_child_idx = i; best_state_idx = s_i; } else if (next_g == best_next_gap) { if (s.profit > best_gain) { best_gain = s.profit; best_child_idx = i; best_state_idx = s_i; } } } } // If we found a valid move, take it if (best_child_idx != -1) { child_used[best_child_idx] = true; current_profit += best_gain; current_gap = best_next_gap; } else { break; // No more children can be added } } return {current_gap, current_profit}; } void solve(int u) { // Solve for all children first (Post-order) for (int v : tree[u]) { solve(v); } // Determine DP states for u // We calculate valid d_in from 0 to K (actually K is strictly limit, so inputs 0..K-1 are relevant? // d_in can be K if we don't pick u, but then we can't extend. // The problem says "more than K days", so gap K is allowed (distance K+1 is bad). // So d_in <= K is valid input. // --- Option 1: Pick u --- // This resets the gap to 0. The outcome is the same regardless of d_in (as long as d_in allows reaching u). State picked_res = get_greedy_outcome(u, 0, true); // --- Option 2: Skip u --- // We calculate this for each possible input d_in for (int d = 0; d <= K; d++) { // If we pick u, it covers all d inputs (since it resets) dp[u][d].push_back(picked_res); // If we skip u if (d < K) { // Can only skip if we don't violate K immediately // d_in passed to greedy is d. It will try to pass d+1 to children. State skipped_res = get_greedy_outcome(u, d, false); if (skipped_res.profit != -1) { dp[u][d].push_back(skipped_res); } } // Prune the list optimize_states(dp[u][d]); } } // --- Reconstruction --- vector<int> final_path; void reconstruct(int u, int d_in, bool parent_picked) { // We need to determine if we picked u or skipped u. // We re-run the logic to see which option yields the optimal profit recorded in DP. // Find expected profit from DP table long long target_profit = -1; // We need the BEST profit from the pareto list that fits our context? // Actually, the caller ensures we are on the optimal path. // But dp[u][d_in] has multiple options (tradeoff between profit and d_out). // However, since we start at root with d_in=0 and pick u, we just maximize profit. // For recursion, we need to know WHICH state was chosen by the parent greedy. // This is complex. // Alternative: The greedy simulation in the parent determined: // 1. Which child to visit // 2. Which state index of that child used // We should pass "Target Profit" or specific decision down? // Let's re-simulate the parent's greedy choice to find the child's requirements. } // Recursive reconstruction that re-runs the greedy logic void reconstruct_greedy(int u, int d_in, bool picking_u) { if (picking_u) final_path.push_back(u); int current_gap = (picking_u ? 0 : d_in); // Re-run the exact greedy loop to find path vector<bool> child_used(tree[u].size(), false); while (true) { int req_in = current_gap + 1; if (req_in > K) break; int best_child_idx = -1; int best_state_idx = -1; int best_next_gap = 100; long long best_gain = -1; for (int i = 0; i < tree[u].size(); i++) { if (child_used[i]) continue; int v = tree[u][i]; for (int s_i = 0; s_i < dp[v][req_in].size(); s_i++) { State& s = dp[v][req_in][s_i]; int next_g = s.d_out + 1; if (next_g < best_next_gap) { best_next_gap = next_g; best_gain = s.profit; best_child_idx = i; best_state_idx = s_i; } else if (next_g == best_next_gap) { if (s.profit > best_gain) { best_gain = s.profit; best_child_idx = i; best_state_idx = s_i; } } } } if (best_child_idx != -1) { child_used[best_child_idx] = true; // Recurse: We must determine if the child was picked or skipped. // We know the child achieved 'best_gain' with output 'best_next_gap-1'. // We check if "Pick" or "Skip" at child generates this. int v = tree[u][best_child_idx]; // Check Pick State pick_res = get_greedy_outcome(v, 0, true); bool is_pick = false; // Note: Pareto optimization might remove exact matches, but we kept optimal ones. // If Pick matches the stats: // However, Skip might also match. Tie-breaking rules in solve must match here. // In optimize_states, we prefer picking if profits equal? No specific order. // Let's check if Pick provides the profit/d_out. // Actually, simply: // Calculate Pick Result at child. // Calculate Skip Result at child (with input req_in). // Compare which one matches 'best_gain' and 'best_next_gap - 1'. // If both match, prefer Pick (arbitrary, but must be consistent with dp build). // In build, we added Pick then Skip. Optimize sort stable? No. // But usually Pick has better profit. State skip_res = {100, -1}; if (req_in < K) skip_res = get_greedy_outcome(v, req_in, false); // Check match // We use >= for profit to capture the "best" choice logic if (pick_res.d_out == best_next_gap - 1 && pick_res.profit == best_gain) { is_pick = true; } else if (skip_res.profit != -1 && skip_res.d_out == best_next_gap - 1 && skip_res.profit == best_gain) { is_pick = false; } else { // Fallback: If optimized out, usually picking is the robust choice for reconstruction is_pick = true; } reconstruct_greedy(v, req_in, is_pick); current_gap = best_next_gap; } else { break; } } } 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]; } // Root is 1 build_tree(1, 0); solve(1); // Initial call: The problem implies we start at City 1 and do business. // So we MUST pick node 1. State res = dp[1][0][0]; // Best result for picking 1 (input 0) // Find the specific result that gave max profit. // Since we forced Pick 1, we look at get_greedy_outcome(1, 0, true) cout << res.profit << endl; reconstruct_greedy(1, 0, true); 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...