結果

問題 No.2328 Build Walls
ユーザー qwewe
提出日時 2025-05-14 13:15:35
言語 C++17
(gcc 13.3.0 + boost 1.87.0)
結果
TLE  
実行時間 -
コード長 12,218 bytes
コンパイル時間 1,422 ms
コンパイル使用メモリ 100,216 KB
実行使用メモリ 89,900 KB
最終ジャッジ日時 2025-05-14 13:16:46
合計ジャッジ時間 6,497 ms
ジャッジサーバーID
(参考情報)
judge3 / judge2
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 3
other AC * 11 TLE * 1 -- * 22
権限があれば一括ダウンロードができます

ソースコード

diff #

#include <iostream>
#include <vector>
#include <queue>
#include <limits>
#include <utility> // For std::pair

// Use long long for capacities and flow values to avoid overflow.
using ll = long long;

// Define a large constant to represent infinity. 
// It should be larger than the maximum possible finite cut capacity.
// The maximum capacity could be sum of all A_ij, roughly (H-2)*W*800.
// For H, W <= 800, this is about 798 * 800 * 800 ~= 5.1 * 10^8.
// Using 10^15 is sufficiently large.
const ll INF = 1e15; 

// Structure to represent an edge in the flow network graph.
struct Edge {
    int to;      // Destination node index
    ll cap;     // Current capacity of the edge
    int rev;     // Index of the reverse edge in the adjacency list of 'to' node
};

// Global variables for the graph and flow algorithm state.
std::vector<std::vector<Edge>> graph; // Adjacency list representation of the graph.
std::vector<int> level;             // Stores level of each node in BFS (for Dinic).
std::vector<int> iter;              // Stores iterator/index for DFS search to optimize (for Dinic).

// Function to add a directed edge and its corresponding reverse edge to the graph.
// This is standard practice for max flow algorithms.
void add_edge(int from, int to, ll cap) {
    // Add the forward edge
    graph[from].push_back({to, cap, (int)graph[to].size()});
    // Add the reverse edge with initial capacity 0.
    // The capacity of the reverse edge increases when flow is pushed back.
    graph[to].push_back({from, 0, (int)graph[from].size() - 1}); 
}


// Breadth-First Search to compute the level graph for Dinic's algorithm.
// Returns true if the sink 't' is reachable from the source 's'.
bool bfs(int s, int t) {
    level.assign(graph.size(), -1); // Initialize all levels to -1 (unvisited).
    std::queue<int> q;
    level[s] = 0; // Level of source node is 0.
    q.push(s);
    while (!q.empty()) {
        int v = q.front();
        q.pop();
        // Explore neighbors of node v
        for (const auto& edge : graph[v]) {
            // If edge has positive capacity and the destination node hasn't been visited yet
            if (edge.cap > 0 && level[edge.to] < 0) {
                level[edge.to] = level[v] + 1; // Set level of neighbor node.
                q.push(edge.to); // Add neighbor node to the queue.
            }
        }
    }
    // Return true if the sink node 't' was reached (level is not -1).
    return level[t] != -1; 
}

// Depth-First Search to find an augmenting path in the level graph and push flow.
// Returns the amount of flow pushed along the path found.
ll dfs(int v, int t, ll f) {
    // Base case: If we reached the sink node 't', return the available flow 'f'.
    if (v == t) return f; 
    
    // Use the iterator 'iter[v]' to optimize DFS. This prevents re-exploring edges
    // that have already been determined to not lead to an augmenting path in this phase.
    for (int& i = iter[v]; i < graph[v].size(); ++i) {
        Edge& e = graph[v][i]; // Reference to the current edge.
        // Check if edge has capacity and leads to a node in the next level.
        if (e.cap > 0 && level[v] < level[e.to]) {
            // Recursively call DFS for the neighbor node. Push minimum of remaining flow 'f' and edge capacity 'e.cap'.
            ll d = dfs(e.to, t, std::min(f, e.cap)); 
            if (d > 0) { // If flow 'd' was successfully pushed down this path
                e.cap -= d; // Decrease capacity of the forward edge.
                graph[e.to][e.rev].cap += d; // Increase capacity of the reverse edge.
                return d; // Return the amount of flow pushed.
            }
        }
    }
    // If no augmenting path is found from node 'v', return 0.
    return 0; 
}

// Computes the maximum flow from source 's' to sink 't' using Dinic's algorithm.
ll max_flow(int s, int t) {
    ll total_flow = 0;
    // Keep finding augmenting paths as long as the sink is reachable from the source in the residual graph.
    while (bfs(s, t)) { // Compute level graph using BFS.
        iter.assign(graph.size(), 0); // Reset DFS iterators for the new level graph.
        ll flow_increment;
        // Use DFS to find and push flow along augmenting paths in the current level graph.
        while ((flow_increment = dfs(s, t, INF)) > 0) {
            total_flow += flow_increment; // Add the pushed flow to the total flow.
        }
    }
     // According to Max-Flow Min-Cut theorem, max flow equals min cut capacity.
     // Check if the calculated flow is effectively infinite. This suggests an issue, possibly an unblockable path
     // that was not caught by the initial BFS check, or total costs sum up to INF (unlikely).
     if (total_flow >= INF) return -1; 
    return total_flow; // Return the total maximum flow.
}

int main() {
    // Use fast I/O operations.
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int H, W; // Read grid dimensions Height and Width.
    std::cin >> H >> W;

    // Read the cost matrix A. Use 1-based indexing for rows and columns as in problem statement.
    // A[i][j] stores the cost to build a wall at cell (i, j).
    // Valid indices for costs are i=2..H-1, j=1..W.
    std::vector<std::vector<ll>> A(H + 1, std::vector<ll>(W + 1));
    for (int i = 2; i <= H - 1; ++i) {
        for (int j = 1; j <= W; ++j) {
            std::cin >> A[i][j];
        }
    }

    // --- Initial Check Phase ---
    // Perform BFS on the grid to check if there exists an "unblockable" path from row 1 to row H.
    // An unblockable path consists ONLY of cells that cannot have walls built on them.
    // These are cells in row 1, row H, or cells (i, j) where 2 <= i <= H-1 and A[i][j] == -1.
    std::queue<std::pair<int, int>> q_check; // Queue for BFS. Stores cell coordinates (row, col).
    std::vector<std::vector<bool>> visited(H + 1, std::vector<bool>(W + 1, false)); // Keep track of visited cells.
    
    // Initialize BFS queue with all cells in row 1. These are the starting points.
    for (int j = 1; j <= W; ++j) {
        if (!visited[1][j]) { 
             q_check.push({1, j});
             visited[1][j] = true;
        }
    }

    bool possible_to_reach_H = false; // Flag to indicate if an unblockable path reaches row H.
    int dr[] = {-1, 1, 0, 0}; // Delta row for adjacent cells: up, down, left, right.
    int dc[] = {0, 0, -1, 1}; // Delta column for adjacent cells.

    // Run the BFS check.
    while (!q_check.empty()) {
        std::pair<int, int> curr = q_check.front();
        q_check.pop();
        int r = curr.first;
        int c = curr.second;

        // If BFS reaches any cell in row H, an unblockable path exists.
        if (r == H) {
            possible_to_reach_H = true;
            break; // Exit BFS early.
        }

        // Explore neighbors of the current cell.
        for (int k = 0; k < 4; ++k) {
            int nr = r + dr[k]; // Neighbor row.
            int nc = c + dc[k]; // Neighbor column.

            // Check if neighbor is within grid bounds and not visited yet.
            if (nr >= 1 && nr <= H && nc >= 1 && nc <= W && !visited[nr][nc]) {
                 bool passable = false; // Check if the neighbor cell is inherently unblockable.
                 if (nr == 1 || nr == H) { // Cells in row 1 and H are always passable (start/end zones, no walls).
                     passable = true;
                 } else { // For cells in intermediate rows (2 to H-1).
                     // Check if wall cannot be built (A[nr][nc] == -1).
                     if (A[nr][nc] == -1) { 
                         passable = true;
                     }
                 }
                 // If the neighbor cell is passable according to the check.
                 if (passable) {
                    visited[nr][nc] = true; // Mark as visited.
                    q_check.push({nr, nc}); // Add to queue for further exploration.
                 }
            }
        }
    }

    // If the BFS check found an unblockable path to row H, output -1 and terminate.
    if (possible_to_reach_H) {
        std::cout << -1 << std::endl;
        return 0;
    }

    // --- Max Flow Phase ---
    // If no unblockable path exists, proceed to calculate the minimum cost to block all paths using max flow.
    int N_cells = H * W; // Total number of cells in the grid.
    int source = 2 * N_cells; // Index for the source node S.
    int sink = 2 * N_cells + 1; // Index for the sink node T.
    graph.resize(2 * N_cells + 2); // Resize the graph adjacency list structure.

    // Lambda functions to map cell coordinates (r, c) to node indices in the graph.
    // Each cell (r,c) is split into an in-node and an out-node.
    auto node_in = [&](int r, int c) {
        // Calculate index for the in-node of cell (r, c).
        return 2 * ((r - 1) * W + (c - 1));
    };
    auto node_out = [&](int r, int c) {
        // Calculate index for the out-node of cell (r, c).
        return 2 * ((r - 1) * W + (c - 1)) + 1;
    };

    // Build the flow network graph based on the grid.
    for (int i = 1; i <= H; ++i) { // Iterate through rows.
        for (int j = 1; j <= W; ++j) { // Iterate through columns.
            int u_in = node_in(i, j); // Get index of in-node for cell (i, j).
            int v_out = node_out(i, j); // Get index of out-node for cell (i, j).
            
            // Add the edge connecting the in-node to the out-node for cell (i, j).
            // The capacity of this edge represents the cost to build a wall at (i, j),
            // or infinity if a wall cannot be built.
            ll cost;
            if (i == 1 || i == H) { // Walls cannot be built in row 1 or H.
                cost = INF;
            } else { // For intermediate rows 2 to H-1.
                cost = A[i][j]; // Cost is given by A[i][j].
                if (cost == -1) { // If A[i][j] is -1, wall cannot be built.
                    cost = INF; // Represent impossibility with infinite capacity.
                }
            }
            add_edge(u_in, v_out, cost); // Add the edge with calculated capacity.

            // Add edges representing possible movement between adjacent cells.
            // These edges have infinite capacity as movement itself is free.
            // Consider only right and down neighbors to add connections for each pair exactly once.
            
            // Check right neighbor (i, j+1)
            if (j + 1 <= W) {
                int neighbor_in = node_in(i, j + 1);
                int neighbor_out = node_out(i, j + 1);
                // Add edge from (i, j)_out to (i, j+1)_in (movement right).
                add_edge(v_out, neighbor_in, INF); 
                // Add edge from (i, j+1)_out to (i, j)_in (movement left).
                add_edge(neighbor_out, u_in, INF); 
            }
            // Check down neighbor (i+1, j)
            if (i + 1 <= H) {
                int neighbor_in = node_in(i + 1, j);
                int neighbor_out = node_out(i + 1, j);
                 // Add edge from (i, j)_out to (i+1, j)_in (movement down).
                add_edge(v_out, neighbor_in, INF);
                // Add edge from (i+1, j)_out to (i, j)_in (movement up).
                add_edge(neighbor_out, u_in, INF);
            }
        }
    }
    
    // Connect the global source node S to all in-nodes of cells in row 1.
    // These represent the possible starting points for Sepa-kun. Infinite capacity edges.
    for (int j = 1; j <= W; ++j) {
        add_edge(source, node_in(1, j), INF);
    }

    // Connect all out-nodes of cells in row H to the global sink node T.
    // These represent the target destination cells for Sepa-kun. Infinite capacity edges.
    for (int j = 1; j <= W; ++j) {
        add_edge(node_out(H, j), sink, INF);
    }

    // Calculate the maximum flow from source to sink using Dinic's algorithm.
    // By the Max-Flow Min-Cut theorem, this value equals the minimum cut capacity.
    ll min_cost = max_flow(source, sink);
    
    // Output the minimum cost. If min_cost is -1 (returned by max_flow function), it means impossible.
    std::cout << min_cost << std::endl;

    return 0;
}
0