Problem Solving/BOJ

[백준 10319번] [네트워크 플로우 / 정점 분할] 좀비 아포칼립스

  • -
728x90
반응형

상당히 재밌게 풀은 문제이다.

 

이 문제를 풀기위해서는 여러 도로들을 거치면서 사람이 지나갈 수 있는 정도를 이해해야한다.

예를 들어 1번에서 2번으로 가기 위한 간선은 매 단위시간마다 2명이 들어설 수 있고 지나가려면 단위 시간 3이 필요하고

2번에서 3번으로 가기 위한 간선은 매 단위시간마다 1명이 들어설 수 있고 지나가려면 단위 시간 5가 필요하다고 하자.

 

그러면 1번에서 3번으로 가기 위해서는 간선 (1,2)와 (2,3)을 거쳐가야하는데 이 과정에서 두번째 간선의 유입량이 1로써 제일 작으므로 사실상 단위 시간당 3번으로 빠져나오는 양은 1이다.

 

즉, 빠져나오는 양은 간선의 단위 시간 당 들어올 수 있는 사람의 수의 최솟값이다.

 

사람의 유입되고 그것을 산출하는 방식이므로 Network flow로 이해할 수 있고

단위 시간 당 들어올 수 있는 사람의 수는 사실상 capacity로 이해할 수 있다.

 

다만, 이 문제가 특이한 지점은 앞서 언급한 것처럼 단위시간이 존재한다는 것이다.

가장 많은 인원을 병원으로 내보내기 위해서는 가장 시간이 덜 걸리는 간선을 타고 이동해야하는데 기존의 일반적으로 다루는 Network flow로는 이 지점을 처리할 수 없다.

 

해결이 안되는 이유는 이동하는데 걸리는 시간에 대한 고려 없이 capacity만 가지고 처리할 수 없기 때문이다. 이러한 측면에서 간선을 분할하는 방식을 생각할 수 있다.

 

간선을 cost만큼 분할하고 Edmonds-Karp 방식을 활용하여 최소 bfs로 계속해서 갱신시킨다는 것의 의미는 최단 시간안에 병원에 갈 수 있는 것을 찾는 것과 동치이다.(결과적으로 BFS를 통해 가장 최단 경로를 찾게 될 것이고, 경로를 한번 타고 이동할 때마다 시간이 1 증가한다는 점을 이용하면 무조건 BFS를 통해 나오는 결과는 최단 시간이 걸리는 Augmenting path라고 보장할 수 있다.)

따라서 해당 augmenting path를 통해 이동할 수 있는 사람의 숫자는 (s - 해당 간선을 통해 병원을 갈 수 있는 최소 시간 + 1) * capacity이다.

 

위의 내용을 활용하여 모든 augmenting path를 구하고 max_flow를 구해주면 된다.

 

다만, 위의 간선을 cost만큼 분할하기 위해서 좀 메모리를 강제로 많이 잡아먹기는 했다.(이 부분은 개선할 수 있는 방향성이 충분히 존재할 것 같으니 고민해보도록 하자.)

 

#include <bits/stdc++.h>
#define fastio ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

using namespace std;
typedef pair<int, int> pii;
const int INF = 987654321;
const int MAX_N = 1002001;
int n;
int start, g, s; // i : 처음에 있는 장소(Source), g : 사람의 수, s : 감염까지 걸리는 시간

struct edge{
    int from, to, capacity, flow;
    edge* reverse_edge;
    edge(int u, int v, int c) : from(u), to(v), capacity(c), flow(0)
    {}
    int residual(){
        return capacity - flow;
    }
    void add_flow(int amount){
        flow += amount;
        reverse_edge -> flow -= amount;
    }
};

vector<edge*> adj[MAX_N];

void add_edge(int u, int v, int c, bool dir = true){
    edge* e1 = new edge(u, v, c);
    edge* e2 = new edge(v, u, dir ? 0 : c);
    e1 -> reverse_edge = e2;
    e2 -> reverse_edge = e1;
    adj[u].push_back(e1);
    adj[v].push_back(e2);
}

int networkFlow(int source, int sink){
    int max_flow = 0;
    while(true){
        vector<edge*> parent(MAX_N, nullptr);
        queue<pii> q;
        q.push(make_pair(source, 0));
        int cost_temp;
        while(!q.empty() && parent[sink] == nullptr){
            int here = q.front().first;
            int cost = q .front().second;
            q.pop();
            for(int i = 0; i < adj[here].size(); i++){
                int there = adj[here][i] -> to;
                if(adj[here][i] -> residual() > 0 && parent[there] == nullptr){
                    q.push(make_pair(there, cost + 1));
                    parent[there] = adj[here][i];
                }
                if(parent[sink] != nullptr){
                    cost_temp = cost;
                }
            }
        }
        if(parent[sink] == nullptr) break;
        if(cost_temp > s) break; // 이미 bfs에서 만족하는 케이스를 넘은 경우 (무조건 뒤는 더 cost가 크므로 break`)
        int amount = INF;
        for(int p = sink; p != source; p = parent[p] -> from){
            amount = min(amount, parent[p] -> residual());
        }
        for(int p = sink; p != source; p = parent[p] -> from){
            parent[p] -> add_flow(amount);
        }
        max_flow += (s - cost_temp + 1) * amount;
    }
    return max_flow;
}

int main(void){
    fastio;
    int test_case;
    cin >> test_case;

    for(int i = 0; i < test_case; i++){
        cin >> n;
        cin >> start >> g >> s;
        int m; // 병원~~
        cin >> m;
        for(int j = 0; j < m; j++){
            int temp;
            cin >> temp;
            add_edge(temp, 1002000, INF);
        }

        int edge_num;
        cin >> edge_num;

        for(int j = 0; j < edge_num; j++){
            int from, to, capacity, cost;
            cin >> from >> to >> capacity >> cost;
            if(cost == 1){
                add_edge(from, to, capacity);
                continue;
            } 
            add_edge(from, 1001 + 1000 * j, capacity);
            for(int k = 0; k < cost - 2; k++){
                add_edge(1001 + 1000 * j + k, 1001 + 1000 * j + k + 1, capacity);
            }
            add_edge(1001 + 1000 * j + cost - 2, to, capacity);
        }
        int result_value = networkFlow(start, 1002000);
        if(result_value > g) cout << g << "\n";
        else cout << result_value << "\n";
        for(int i = 0; i < MAX_N; i++){
            adj[i].clear();
        }
    }

    return 0;
}

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.