[Baekjoon,1420] 학교 가지마!

2 분 소요

이 문제는 각 위치를 정점으로 하고 시작 위치를 소스, 도착 위치를 싱크라고 했을 때 최소 컷을 구하면 되므로 최대 유량 최소 컷 정리 에 따라 최대 유량을 구하면 된다. 그런데 무작정 칸들에 대해 최대유량을 구하면 벽이 칸들을 잇는 길에 놓일 수 있다. 이때 간선을 막는 것이 아닌 정점, 즉 벽을 놓았을 때 이 막혀야 하는 것이기 때문에 각 정점을 둘로 쪼개야 한다. 이 방법으로 길이 아닌 한 칸에 벽을 놓아야 하는 것을 해결할 수 있다.

용량은 다음과 같이 설정한다. 둘로 쪼개어진 정점의 간선만 용량을 1로 설정하고 모든 길은 마음껏 지나갈 수 있기 때문에 길을 잇는 간선의 용량은 무한대로 한다. 증가 경로를 찾는 중 역방향 유량의 계산을 위해서 반대 방향의 용량은 0으로 설정한다.

최적화

1. 각 정점에 연결된 간선이 얼마 없다.

각 위치에는 상,하,좌,우 네 가지의 방향이 연결되어 있을 수 있다. 이 때문에 flow를 인접 행렬로 구현하면 이용 되지 않는 간선이 너무 많아 메모리가 낭비된다. 이는 메모리 초과 라는 결과를 일으킨다. 이것을 해결하는 방법은 두 정점을 key 값으로 하고 현재 유량을 value로 한 map을 생성하는 것이다.

2. 쪼개진 정점의 용량의 크기는 1이다.

이를 이용하여 용량을 따로 구현하지 않고 유량만을 이용하여 구현 할 수 있다. 유량이 1이 된 경우 이미 유량이 다 찬 것으로 생각할 수 있으며 용량이 무한대인 경우에는 해당 유량을 음의 무한대로 설정하면 된다. 이러한 최적화는 쪼개진 정점의 용량의 크기가 1로 고정되어 있기 때문에 할 수 있다.

구현

각 위치에 대응되는 정점 번호를 구하는 함수를 만들어 구현한 것을 볼 수 있다.

#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <cmath>
using namespace std;

const int NEG_INF=-987654321;
const int MAX_V=20002;
const int OUT=10000;

const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};

int N,M,SRC,SNK;
vector<int> adj[MAX_V];
map<pair<int,int>,int> flow;

inline bool inRange(int y, int x)
{
    return 0<=y && y<N && 0<=x && x<M;
}

// 각 정점을 위치에 대응 시킨다.
inline int convert(int y, int x)
{
    return y*M+x;
}

void setGraph(const vector<string>& map)
{
    for(int y=0;y<N;++y)
        for(int x=0;x<M;++x)
        {
            if(map[y][x]=='#')
                continue;
            int here=convert(y,x);
            // 쪼개진 정점은 In -> Out 으로 이어져 있다. 
            // 역방향으로 흐르는 유량이 있을 수 있으니 
            // 반대쪽 정점도 추가하여야 한다. 
            adj[here].push_back(here+OUT);
            adj[here+OUT].push_back(here);
            
            flow[{here,here+OUT}]=0;
            flow[{here+OUT,here}]=1;
            for(int dir=0;dir<4;++dir)
            {
                int ny=y+dy[dir];
                int nx=x+dx[dir];
                if(inRange(ny,nx) && map[ny][nx]!='#')
                {
                    int there=convert(ny,nx);
                    
                    adj[here+OUT].push_back(there);
                    adj[there].push_back(here+OUT);
                    
                    flow[{here+OUT,there}]=NEG_INF;
                    flow[{there,here+OUT}]=1;
                }
                
                if(map[y][x]=='K')
                {
                    SRC=here;
                    flow[{here,here+OUT}]=NEG_INF;
                }
                else if(map[y][x]=='H')
                {
                    SNK=here;
                    flow[{here,here+OUT}]=NEG_INF;
                }
            }
        }
}

int getMinBlock()
{
    int totalFlow=0;
    int V=N*M*2;
    while(true)
    {
        queue<int> q;
        vector<int> parent(MAX_V,-1);
        q.push(SRC);
        parent[SRC]=SRC;
        // BFS로 증가경로를 찾는다. 
        while(!q.empty() && parent[SNK]==-1)
        {
            int here=q.front();
            q.pop();
            for(int i=0;i<adj[here].size();++i)
            {
                int there=adj[here][i];
                if(parent[there]==-1 && flow[{here,there}]<1)
                {
                    parent[there]=here;
                    q.push(there);
                }
            }
        }
        // 더 이상 증가 경로를 찾을 수 없을 때 
        if(parent[SNK]==-1)
            break;
        // 증가 경로에 유량을 흘려 보낸다.
        for(int p=SNK;p!=SRC;p=parent[p])
        {
            flow[{parent[p],p}]++;
            flow[{p,parent[p]}]--;
        }
        ++totalFlow;
    }
    return totalFlow;
}

bool canStop()
{
    if(SRC-M==SNK || SRC+M==SNK || 
       (abs(SRC-SNK)==1 && SRC/M==SNK/M))
        return false;
    return true;
}

int main()
{
    vector<string> map;
    cin>>N>>M;
    for(int i=0;i<N;++i)
    {
        string s;
        cin>>s;
        map.push_back(s);
    }
    
    setGraph(map);
    if(canStop())
        cout<<getMinBlock()<<endl;
    else
        cout<<-1<<endl;
    
    return 0;
}

댓글남기기