結果
問題 |
No.448 ゆきこーだーの雨と雪 (3)
|
ユーザー |
![]() |
提出日時 | 2025-05-14 13:03:02 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 118 ms / 2,000 ms |
コード長 | 12,120 bytes |
コンパイル時間 | 670 ms |
コンパイル使用メモリ | 83,960 KB |
実行使用メモリ | 18,048 KB |
最終ジャッジ日時 | 2025-05-14 13:04:23 |
合計ジャッジ時間 | 5,806 ms |
ジャッジサーバーID (参考情報) |
judge4 / judge1 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 4 |
other | AC * 35 |
ソースコード
#include <iostream> #include <vector> #include <algorithm> using namespace std; // Define long long type for large numbers typedef long long ll; // Use a sufficiently large value for infinity. Maximum possible sum is N * maxD ~ 2e5 * 1e9 = 2e14. // 4e18 ensures it's larger than any possible sum and fits within 64-bit signed integer. const ll INF = 4e18; // Structure to hold task information struct Task { ll T, D; // Time and Difficulty }; /** * @brief Check function to determine if it's possible to assign tasks such that * Ame only performs tasks with difficulty <= X, respecting Yuki's cooldown K. * * @param X The maximum difficulty threshold for Ame. * @param N The number of tasks. * @param K Yuki's required rest time (cooldown). * @param tasks Vector of tasks sorted by time. * @return true if such an assignment is possible, false otherwise. */ bool check(ll X, int N, ll K, const vector<Task>& tasks) { // Tracks the time Yuki finished her last assigned task. Use -INF to represent initial state. ll last_yuki_task_time = -INF; for (int i = 0; i < N; ++i) { ll Ti = tasks[i].T; ll Di = tasks[i].D; // Check if Ame can perform this task based on difficulty threshold X bool ame_possible = (Di <= X); // Check if Yuki is available to perform this task bool yuki_available; if (last_yuki_task_time == -INF) { yuki_available = true; // Yuki is always available initially } else { // Check if current task time Ti is at least K time units after Yuki's last task finish time. // Use subtraction `Ti - K >= last_yuki_task_time` to avoid potential overflow with `last_yuki_task_time + K` if K is very large. if (last_yuki_task_time > Ti) { // Optimization: if last Yuki task was later than current task time yuki_available = false; // Technically impossible, but helps with large K. } else if (K > Ti) { // K is larger than current time, Ti - K could underflow. Handle carefully. if (last_yuki_task_time <= Ti - K) { // If Ti - K is negative, this comparison works correctly yuki_available = true; } else { yuki_available = false; } } else if (Ti - K >= last_yuki_task_time) { yuki_available = true; } else { yuki_available = false; } } if (!ame_possible) { // Task difficulty Di > X, Ame cannot perform it. if (!yuki_available) { // Yuki must perform the task, but she's unavailable. Impossible state. return false; } // Yuki must perform the task and she is available. Update her last task time. last_yuki_task_time = Ti; } else { // Task difficulty Di <= X, Ame can perform it. if (!yuki_available) { // Ame must perform task i because Yuki is unavailable. // Yuki's last task time remains unchanged. } else { // Both Ame and Yuki can perform the task. // The check function aims for feasibility. To maximize future feasibility, we should keep Yuki available as much as possible. // This translates to minimizing the `last_yuki_task_time` state variable. // If Ame performs the task, Yuki's state remains `last_yuki_task_time`. // If Yuki performs the task, Yuki's state becomes `Ti`. // We choose the action that results in the minimum `last_yuki_task_time`. if (Ti <= last_yuki_task_time) { // If assigning to Yuki results in an earlier or same availability time, choose Yuki. last_yuki_task_time = Ti; } // else { // Assigning to Ame keeps the earlier availability time. State remains `last_yuki_task_time`. No state change needed. } } } } // If all tasks could be assigned respecting constraints, return true. return true; } // Segment tree structure for Range Minimum Query struct Node { ll val; Node() : val(INF) {} // Initialize nodes with infinity }; vector<Node> tree; // Segment tree storage int N_seg; // Size of the segment tree base array = N+1 // Build segment tree recursively void build(int node, int start, int end) { if (start == end) { // Initialize leaf node 0 (base case before any tasks) with value 0, others with INF tree[node].val = (start == 0) ? 0 : INF; } else { int mid = start + (end - start) / 2; build(2 * node, start, mid); build(2 * node + 1, mid + 1, end); // Internal node stores the minimum of its children tree[node].val = min(tree[2 * node].val, tree[2 * node + 1].val); } } // Update segment tree at index idx with value void update(int node, int start, int end, int idx, ll value) { // Check if index is valid for current node range if (idx < start || idx > end) return; if (start == end) { // Leaf node found tree[node].val = value; } else { int mid = start + (end - start) / 2; if (idx <= mid) { // Index is in the left child range update(2 * node, start, mid, idx, value); } else { // Index is in the right child range update(2 * node + 1, mid + 1, end, idx, value); } // Update internal node value after child update tree[node].val = min(tree[2 * node].val, tree[2 * node + 1].val); } } // Query minimum value in range [l, r] ll query(int node, int start, int end, int l, int r) { // If query range is invalid or outside current node range, return INF if (r < start || end < l || l > r) { return INF; } // If current node range is fully contained within query range, return node value if (l <= start && end <= r) { return tree[node].val; } // Recursively query children and return minimum int mid = start + (end - start) / 2; ll p1 = query(2 * node, start, mid, l, r); ll p2 = query(2 * node + 1, mid + 1, end, l, r); return min(p1, p2); } int main() { // Faster I/O ios_base::sync_with_stdio(false); cin.tie(NULL); int N; // Number of tasks ll K; // Yuki's cooldown time cin >> N >> K; vector<Task> tasks(N); ll maxD = 0; // Keep track of maximum difficulty for binary search range for (int i = 0; i < N; ++i) { cin >> tasks[i].T >> tasks[i].D; maxD = max(maxD, tasks[i].D); } ll low = 0, high = maxD, min_max_diff = -1; // Binary search range for minimum max difficulty M // Check if M=0 is possible first bool M0_possible = check(0, N, K, tasks); if (M0_possible) { min_max_diff = 0; // If M=0 possible, that's the minimum possible M } else { // If M=0 not possible, binary search for the smallest M > 0 low = 1; // Start search from 1 while (low <= high) { ll mid = low + (high - low) / 2; if (mid == 0) { // Safety check, should not occur if low starts at 1 low = 1; continue; } if (check(mid, N, K, tasks)) { min_max_diff = mid; // Found a possible M, try smaller high = mid - 1; } else { low = mid + 1; // mid is too small, need larger M } } } // If M=0 is the answer, output 0 0 and exit if (min_max_diff == 0) { cout << 0 << endl; cout << 0 << endl; return 0; } // If binary search failed to find any valid M > 0 (min_max_diff still -1), it indicates an issue. // However, check(maxD) should always be true if N>=1, guaranteeing a solution M exists. // This path should theoretically not be reached under problem constraints. if (min_max_diff == -1) { // This case shouldn't happen with N>=1. Maybe output error or default? // Let's assume problem guarantees solution existence. min_max_diff = maxD; // Fallback? Or maybe it implies check(maxD) itself failed? } // Calculate Minimum Total Difficulty S for the found M = min_max_diff using DP with Segment Tree optimization vector<ll> dpA(N + 1, INF); // dpA[i] min cost ending with Ame doing task i-1 vector<ll> dpY(N + 1, INF); // dpY[i] min cost ending with Yuki doing task i-1 dpA[0] = 0; dpY[0] = 0; // Base case: cost is 0 before any task // Precompute prefix sums of difficulties vector<ll> PS_D(N + 1, 0); for(int k = 0; k < N; ++k) { PS_D[k+1] = PS_D[k] + tasks[k].D; } // Precompute last invalid task index for each task i // prev_invalid_0based[i] = largest index p < i s.t. tasks[p].D > M. If none, -1. vector<int> prev_invalid_0based(N, -1); int last_invalid = -1; for(int k = 0; k < N; ++k) { prev_invalid_0based[k] = last_invalid; if (tasks[k].D > min_max_diff) { last_invalid = k; } } // Setup segment tree for DP optimization N_seg = N + 1; // Need states 0 to N tree.resize(4 * N_seg); build(1, 0, N_seg - 1); // Build tree, initializes state 0 value int p_idx = -1; // Two pointer: Max 0-based task index j such that T_j <= T_i - K // Main DP loop for (int i = 0; i < N; ++i) { // Process tasks 0 to N-1 ll Ti = tasks[i].T; ll Di = tasks[i].D; // Calculate dpA[i+1]: Minimum cost if Ame does task i if (Di <= min_max_diff) { // Ame can only do task i if difficulty <= M ll prev_cost = min(dpA[i], dpY[i]); // Cost up to task i-1 if (prev_cost != INF) { dpA[i+1] = prev_cost + Di; } } // else dpA[i+1] remains INF // Calculate dpY[i+1]: Minimum cost if Yuki does task i // Update two pointer `p_idx` to find the largest index j such that tasks[j].T <= Ti - K while (p_idx + 1 < i && tasks[p_idx + 1].T <= Ti - K) { p_idx++; } int idx_max = p_idx; // largest valid index j for Yuki's previous task // Minimum cost transitioning from a state where Yuki did task j >= 0 ll cost_from_j = INF; // Query range for state index k = j+1: starts after last invalid task, ends at max valid j+1 index. int query_l = prev_invalid_0based[i] + 1; int query_r = idx_max + 1; if (query_l <= query_r) { // Ensure query range is valid // Query segment tree for min(mcost[k] - PS_D[k]) in range [query_l, query_r] ll min_V_k = query(1, 0, N_seg - 1, query_l, query_r); if (min_V_k != INF) { // The DP transition: dpY[i+1] = PS_D[i] + min_V_k cost_from_j = PS_D[i] + min_V_k; } } // Minimum cost transitioning from the initial state (j=-1, state index 0) ll cost_from_neg1 = INF; // This transition is valid only if no task k in [0, i-1] has difficulty > M if (prev_invalid_0based[i] == -1) { cost_from_neg1 = PS_D[i]; // Cost is sum of D_k for k=0..i-1 starting from state 0 (cost 0) } // dpY[i+1] is the minimum of the two possible transitions dpY[i+1] = min(cost_from_j, cost_from_neg1); // Update segment tree with the value for state i+1 (after task i) ll mcost_i1 = min(dpA[i+1], dpY[i+1]); // minimum cost after task i if (mcost_i1 != INF) { // Update tree at index i+1 with value mcost[i+1] - PS_D[i+1] update(1, 0, N_seg - 1, i + 1, mcost_i1 - PS_D[i+1]); } // else: If state i+1 is unreachable (INF), no need to update, segtree node remains INF. } // Final answer is the minimum cost after processing all N tasks ll min_total_diff = min(dpA[N], dpY[N]); // Output the results cout << min_max_diff << endl; cout << min_total_diff << endl; return 0; }