結果

問題 No.448 ゆきこーだーの雨と雪 (3)
ユーザー qwewe
提出日時 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
権限があれば一括ダウンロードができます

ソースコード

diff #

#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;
}
0