結果

問題 No.2366 登校
ユーザー qwewe
提出日時 2025-05-14 12:58:30
言語 C++17
(gcc 13.3.0 + boost 1.87.0)
結果
AC  
実行時間 516 ms / 4,000 ms
コード長 12,837 bytes
コンパイル時間 1,490 ms
コンパイル使用メモリ 132,360 KB
実行使用メモリ 324,864 KB
最終ジャッジ日時 2025-06-20 11:31:59
合計ジャッジ時間 3,437 ms
ジャッジサーバーID
(参考情報)
judge4 / judge5
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 2
other AC * 26
権限があれば一括ダウンロードができます

ソースコード

diff #

#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <cmath>
#include <vector> 
#include <utility> // For std::pair
#include <algorithm> // for std::prev

using namespace std;

// Use long long for time and fatigue values which can be large
typedef long long ll;

// Define a large constant to represent infinity. 
// This needs to be large enough to not interfere with actual path costs/times.
// Given the constraints (T, C_i, D_i up to 10^9, grid size up to 80x80),
// path times and fatigues can reach values around 10^12 or higher.
// 4e18 is a safe upper bound (larger than max possible ll value / 2).
const ll INF = 4e18; 

// State structure for the priority queue in Dijkstra's algorithm
struct State {
    ll fatigue; // Current accumulated fatigue
    ll time;    // Current time elapsed (can be negative due to time rewind)
    int node_idx; // Index of the current key location (in key_locations vector)

    // Custom comparison operator for the priority queue (min-heap).
    // Prioritizes states with lower fatigue. If fatigue is equal, prioritizes lower time.
    bool operator>(const State& other) const {
        if (fatigue != other.fatigue) {
            return fatigue > other.fatigue; // Minimum fatigue first
        }
        return time > other.time; // Then minimum time
    }
};


int N, M, K; // Grid dimensions, number of mysterious cells
ll T;       // Time limit
// Map from the index of a key location (if it's a mysterious cell) to its parameters {C, D}
// Indices 1 to K correspond to the mysterious cells.
map<int, pair<ll, ll>> mysterious_cell_params; 

vector<pair<int, int>> key_locations; // Stores coordinates {row, col} of key locations: P0 (start), P1..PK (mysterious), P(K+1) (end)
vector<vector<ll>> adj_dist; // Adjacency matrix storing shortest path distances (time cost) between key locations using only Action 1 (movement).

// Helper function to compute Manhattan distance between two grid cells
// This represents the minimum time to travel between them using only Action 1.
ll grid_dist(pair<int, int> start, pair<int, int> end) {
     // Calculate Manhattan distance: |row1 - row2| + |col1 - col2|
     // Cast differences to ll before adding to avoid potential intermediate overflow if N, M were very large, though not needed for N,M <= 80.
     return (ll)abs(start.first - end.first) + (ll)abs(start.second - end.second);
}


int main() {
    // Optimize C++ standard streams for faster input/output
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    // Read input values
    cin >> N >> M >> K >> T;
    
    // Initialize key locations vector
    key_locations.push_back({1, 1}); // Add start location P0 at index 0
    // Read K mysterious cells and store their info
    for (int i = 0; i < K; ++i) {
        int r, c;
        ll C_val, D_val;
        cin >> r >> c >> C_val >> D_val;
        key_locations.push_back({r, c}); // Add mysterious cell Pi at index i+1
        // Map the node index (i+1) to its parameters C and D
        mysterious_cell_params[i + 1] = {C_val, D_val}; 
    }
    key_locations.push_back({N, M}); // Add end location P(K+1) at index K+1

    int num_key_locations = K + 2; // Total number of key locations
    adj_dist.resize(num_key_locations, vector<ll>(num_key_locations));

    // Precompute shortest path distances (Manhattan distances) between all pairs of key locations
    for (int i = 0; i < num_key_locations; ++i) {
        for (int j = 0; j < num_key_locations; ++j) {
             adj_dist[i][j] = grid_dist(key_locations[i], key_locations[j]);
        }
    }
    
    // Initialize Dijkstra's algorithm
    // Priority queue stores states {fatigue, time, node_idx}, ordered by min fatigue, then min time.
    priority_queue<State, vector<State>, greater<State>> pq;
    
    // 'dist[k]' stores a map for key location k: maps fatigue level -> minimum time achieved for that fatigue level.
    // This stores the non-dominated (Pareto optimal) states reached at each key location.
    vector<map<ll, ll>> dist(num_key_locations);

    // Start state: At P0 (index 0) with 0 fatigue and 0 time.
    dist[0][0] = 0;
    pq.push({0, 0, 0}); // Add initial state to the priority queue

    ll final_min_fatigue = -1; // Variable to store the minimum fatigue found that satisfies the time limit T. Initialize to -1 (not found).

    // Main Dijkstra loop
    while (!pq.empty()) {
        // Get the state with the minimum fatigue (and minimum time for ties) from the priority queue
        State current = pq.top();
        pq.pop();

        ll fatigue = current.fatigue;
        ll time = current.time;
        int u = current.node_idx; // Current key location index

        // Optimization: If we have already found a path to this state (u, fatigue) with a better or equal time, skip processing this one.
        // Check if fatigue exists in the map for node u AND the recorded time is strictly less than the current state's time.
        // If dist[u][fatigue] == time, it means this is the state we added earlier, so proceed.
        if (dist[u].count(fatigue) && dist[u][fatigue] < time) {
            continue;
        }
        
        // Check if the destination node (P(K+1), index K+1) is reached
        if (u == K + 1) {
             // If the time taken is within the limit T
             if (time <= T) {
                 // Since the priority queue processes states in increasing order of fatigue,
                 // this is the first time we reach the destination satisfying the time constraint.
                 // Therefore, this path has the minimum possible fatigue.
                 final_min_fatigue = fatigue;
                 break; // Found the optimal solution, exit the loop.
             }
             // If time > T, this specific path is too slow. We need to continue searching.
             // Since we are at the destination, we don't need to explore neighbors from here.
             continue; 
        }
        
        // Pruning optimization: If we have already found *a* valid path to the destination (final_min_fatigue != -1),
        // any state we extract from the queue with fatigue >= final_min_fatigue cannot lead to a better solution (lower fatigue).
        // So, we can prune this path.
        if (final_min_fatigue != -1 && fatigue >= final_min_fatigue) {
            continue;
        }

        // Explore transitions from the current state (u, fatigue, time):

        // 1. Transition via Action 1: Move from current key location P_u to another key location P_v.
        for (int v = 0; v < num_key_locations; ++v) {
            ll travel_time = adj_dist[u][v]; // Time cost for movement
            ll new_time = time + travel_time; // Calculate new time
            ll new_fatigue = fatigue; // Fatigue does not change with movement

            // Dominance Check: Determine if the new state (v, new_fatigue, new_time) is potentially useful or if it's dominated by existing states at node v.
            bool dominated = false;
            // Find the first element in dist[v] with fatigue level >= new_fatigue
            auto it_lower = dist[v].lower_bound(new_fatigue); 
            
            // Check if an existing state at node v has the *same* fatigue but less or equal time.
            if (it_lower != dist[v].end() && it_lower->first == new_fatigue) {
                 if (it_lower->second <= new_time) { 
                     dominated = true;
                 }
             }
            // Check if an existing state at node v has *strictly less* fatigue and less or equal time.
            // We need to look at the element *before* it_lower.
            if (!dominated && it_lower != dist[v].begin()) {
                 auto it_prev = prev(it_lower); // Iterator to the element with the largest fatigue < new_fatigue
                 if(it_prev->second <= new_time){ // If its time is also <= new_time, it dominates.
                    dominated = true;
                 }
            }

            // If the new state is not dominated:
            if (!dominated) {
                // Update the distance map: record the new minimum time for this fatigue level at node v.
                dist[v][new_fatigue] = new_time;
                // Add the new state to the priority queue for further exploration.
                pq.push({new_fatigue, new_time, v});

                // Clean up dominated states: Remove any existing states at node v that are now dominated by this new state (new_fatigue, new_time).
                // A state (f', t') is dominated if f' >= new_fatigue AND t' >= new_time (and it's not the same state).
                vector<ll> fatigues_to_remove; // Store keys (fatigues) to remove safely after iteration
                // Find the first element with fatigue > new_fatigue
                auto it_upper = dist[v].upper_bound(new_fatigue); 
                // Iterate through all states with fatigue > new_fatigue
                for(auto it = it_upper; it != dist[v].end(); ++it) {
                    // If this state's time is also >= new_time, it's dominated.
                    if (it->second >= new_time) { 
                       fatigues_to_remove.push_back(it->first);
                    }
                }
                // Perform the removal of dominated states.
                for (ll f_remove : fatigues_to_remove) {
                    dist[v].erase(f_remove);
                }
            }
        } // End of loop exploring Action 1 transitions

        // 2. Transition via Action 2: Use the time-rewind ability if P_u is a mysterious cell.
        // Check if the current node index 'u' corresponds to a mysterious cell (indices 1 to K).
        if (mysterious_cell_params.count(u)) { 
             // Retrieve the C and D parameters for this mysterious cell
             ll C_val = mysterious_cell_params[u].first;
             ll D_val = mysterious_cell_params[u].second;
             
             // Calculate the effect of using Action 2 once
             ll time_change = 1 - C_val; // Time changes by 1 - C_i (can be negative)
             ll fatigue_increase = D_val; // Fatigue increases by D_i (>= 1)

             // Basic check to prevent potential overflow when adding fatigue.
             // If current fatigue is already extremely large, adding more might exceed ll limits.
             if (fatigue > INF - fatigue_increase) continue; // Skip if overflow might occur

             ll new_fatigue = fatigue + fatigue_increase; // Calculate new fatigue
             ll new_time = time + time_change; // Calculate new time
             
             // Dominance Check for the resulting state (u, new_fatigue, new_time) at the *same* node u.
             bool dominated = false;
             auto it_lower = dist[u].lower_bound(new_fatigue); // Find first state with fatigue >= new_fatigue
             // Check state with same fatigue
             if(it_lower != dist[u].end() && it_lower->first == new_fatigue) {
                 if (it_lower->second <= new_time) dominated = true;
             }
             // Check states with strictly smaller fatigue
            if(!dominated && it_lower != dist[u].begin()){
                 auto it_prev = prev(it_lower);
                 if (it_prev->second <= new_time) dominated = true;
            }

             // If the resulting state is not dominated:
             if (!dominated) {
                 // Update the distance map for node u with the new state
                dist[u][new_fatigue] = new_time;
                 // Add the new state (staying at node u, but with updated fatigue and time) back to the priority queue
                pq.push({new_fatigue, new_time, u});

                 // Clean up: Remove states at node u that are now dominated by this new state (new_fatigue, new_time).
                vector<ll> fatigues_to_remove;
                auto it_upper = dist[u].upper_bound(new_fatigue); // Find first state with fatigue > new_fatigue
                 // Iterate through states with fatigue > new_fatigue
                 for(auto it = it_upper; it != dist[u].end(); ++it) {
                     // If time is also >= new_time, it's dominated
                     if (it->second >= new_time) {
                        fatigues_to_remove.push_back(it->first);
                     }
                 }
                 // Perform removals
                 for (ll f_remove : fatigues_to_remove) {
                     dist[u].erase(f_remove);
                 }
             }
        } // End of check for Action 2 transition

    } // End of Dijkstra's main loop

    // After the loop finishes (either by finding the optimal solution or exhausting the queue), output the result.
    cout << final_min_fatigue << endl;

    return 0; // Indicate successful execution
}
0