문제
격자로 된 지도에 n명의 어린이와 n개의 집이 있다. 각각의 단위 시간동안, 모든 어린이가 한 단위만큼 움직일 수 있다. (수평이나 수직의 인접한 점으로) 당신은 각각의 어린이가 집에 들어갈 때까지, 1번 움직일 때마다 1달러의 여행비를 지불해야한다. 각각의 집에는 오직 한 명의 어린이만 들어갈 수 있다.
당신의 목표는 n명의 어린이들을 n개의 다른 집으로 보내는데 지불해야하는 비용을 최소화 하는 것이다. 입력은 지도인데, '.' 은 빈 공간을 의미하고 'H'는 집을 의미하고 'm' 은 어린이를 의미한다.
격자로 된 지도에서 각각의 점을 격자라고 간주한다. 그리고 이 격자에 n명의 어린이를 동시에 놓을 수 있다. 또한, 한 어린이가 집으로 들어가지 않고 집의 격자로 움직이는 것은 가능하다.
입력
입력으로 하나에서 여러 개의 테스트 케이스가 있다. 각각의 케이스의 첫 줄은 N과 M의 두개의 정수이다. N은 지도의 행(row)의 개수이고, M은 지도의 열(column)의 개수이다. 입력의 나머지는 N개의 줄 지도를 묘사한다. N와 M은 둘다 2~100 사이로 가정한다. 지도에서 'H'와 'm'의 개수는 같다. 그리고 최대 100개의 집이 있을 수 있다.
N과 M 을 0 0 으로 입력하면 입력이 종료된다.
출력
각각의 테스트 케이스에서 출력의 첫 줄은 하나의 정수이다. 그것은 지불해야할 최소한의 달러의 양이다.
각 좌표에서 모든 어린이들이 집에 각각 하나씩 매칭되어 들어가야합니다.
이때, 한 격자씩 움직일 때마다 비용은 1달러인데, 모든 어린이들이 집을 가는데 최소비용을 구해야합니다.
MCMF문제입니다.
SPFA알고리즘을 사용합니다.
#include<bits/stdc++.h>
#define FAST ios_base::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
#define mset(v) memset(v,0,sizeof(v));
#define rep(i,a) for(int i=0;i<a;++i)
#define REP(i,a) for(int i=1;i<=a;++i)
using namespace std;
typedef long long ll;
typedef pair<int, int> pi;
typedef tuple<int, int, int>ti;
typedef vector<int> vi;
typedef vector<vector<int>> vvi;
int dy[] = { -1,0,1,0 }, dx[] = { 0,1,0,-1 }, INF = 987654321;
struct Edge {
int t;
int c;
int f = 0;
int cost = 0;
Edge* rev;
Edge(int to, int capacity,int costs) {
t = to;
c = capacity;
cost = costs;
}
int R() {
return c - f;
}
void F(int amount) {
f += amount;
rev->f -= amount;
}
};
int n, m, S = 10001, E = 10002;
vector<Edge*>adj[10004];
void AddEdge(int f, int t, int c,int costs) {
Edge* e = new Edge(t, c, costs);
Edge* rev = new Edge(f, 0 ,-costs);
e->rev = rev;
rev->rev = e;
adj[f].push_back(e);
adj[t].push_back(rev);
}
int main() {
FAST;
while (cin >> n >> m) {
rep(i, 10004)adj[i].clear();
int ans = 0;
int cnt = 0;
if (n == 0 && m == 0)break;
rep(i, n) {
rep(j, m) {
char c;
cin >> c;
if (c == 'm') {
cnt++;
int f = S;
int t = i * m + j;
Edge* e = new Edge(t, 1 , 0);
Edge* rev = new Edge(f, 0 , 0);
e->rev = rev;
rev->rev = rev;
adj[f].push_back(e);
adj[t].push_back(rev);
}
else if (c == 'H') {
int f = i * m + j;
int t = E;
Edge* e = new Edge(t, 1 , 0);
Edge* rev = new Edge(f, 0, 0);
e->rev = rev;
rev->rev = rev;
adj[f].push_back(e);
adj[t].push_back(rev);
}
rep(k, 4) {
int ny = i + dy[k];
int nx = j + dx[k];
if (ny >= 0 && ny < n && nx >= 0 && nx < m) {
AddEdge(i * m + j, ny * m + nx, INF,1);
}
}
}
}
while(1){
queue<int>q;
int inQ[10004];
int dist[10004];
int p[10004];
Edge* FromEdge[10004];
memset(p, -1, sizeof(p));
memset(dist, 0x7f, sizeof(dist));
mset(inQ);
q.push(S);
dist[S] = 0;
inQ[S] = 1;
while (!q.empty()) {
int cur = q.front();
q.pop();
inQ[cur] = 0;
for (auto e : adj[cur]) {
int to = e->t;
if (dist[to] > dist[cur] + e->cost && e->R() > 0) {
p[to] = cur;
dist[to] = dist[cur] + e->cost;
FromEdge[to] = e;
if (!inQ[to]) {
inQ[to] = 1;
q.push(to);
}
}
}
}
if (p[E] == -1)break;
for (int i = E; i != S; i = p[i]) {
Edge* e = FromEdge[i];
e->F(1);
ans += e->cost;
}
}
cout << ans << "\n";
}
}
정점의 개수가 10000개이므로, 배열로 간선의 capacity와 flow를 저장할 수 없습니다.
따라서 간선 struct를 선언합니다.
이 문제가 까다로웠던점은,,,,
1.격자좌표이므로 각 정점의 위치는 i * m + j 번 입니다.
2.간선은 양방향입니다. 양방향 간선을 표현하는데 역방향 간선을 포함하지 않습니다. 이는 A정점과 B정점이 총 4개의 간선으로 연결되어있다는 것입니다.
1. A->B : capacity = 1 , cost = 1
2. A->B rev:capacity = 0, cost = -1
3. B->A : capacity =1 , cost = 1
4. B->A rev: capacity=0 , cost = -1
역방향 간선의 capacity 는 0이고 cost는 - 입니다.
3.SFPA 알고리즘을 사용할때 배열 p와 FromEdge를 사용합니다.
p[x]는 x의 부모 노드를 저장합니다.
FromEdge[x]는 x로 향하는 간선을 저장합니다.
'baekjoon' 카테고리의 다른 글
[백준] 13505 두 수 XOR (0) | 2023.06.28 |
---|---|
[백준] 5670 휴대폰 자판 (0) | 2023.06.28 |
[백준] 11670 초등 수학 (0) | 2023.06.26 |
[백준] 1305 광고 (0) | 2023.06.22 |
[백준] 10256 돌연변이 (0) | 2023.06.22 |