문제


https://www.acmicpc.net/problem/11495


풀이


격자를 체스판처럼 생각해 두 부분으로 나눈다.

즉 행과 열의 홀짝이 같은 칸들과 다른 칸들로 나눈다.

인접한 정수 2개를 고르는 것은 이 두 부분에서 하나씩 고르는 것이므로 한 부분에서 다른 부분으로 흐르는 flow network를 생각할 수 있다.

source에서 한 부분으로 가는 엣지의 capacity를 각 칸의 정수로 놓고, 다른 부분에서 sink로 가는 엣지의 capacity 또한 각 칸의 정수로 놓는다.

한 부분에서 다른 부분으로 가는 엣지들의 capacity는 충분히 크기만 하면 된다.

그러면 최대 flow는, 최소 연산으로 모든 정수를 0으로 만드는 과정에서, 어느 인접한 두 정수도 모두 양수이지 않을 때까지의 연산 횟수이다.

다시 말하자면, 최대 flow만큼 연산을 적용시킨 후엔 한 칸씩 1 감소시켜야 한다.

그래서 격자 위 남은 모든 양수들의 합만큼 연산을 더 해주면 된다.

정답의 최소성이 보장되는 이유는 flow network의 성질에서 기인한다.


코드


#include <iostream>
#include <string.h>
#include <algorithm>
#include <queue>
#include <utility>
#include <vector>

using namespace std;

typedef pair<int, int> pii;
int n, m, ans;
int board[50][50], graph[2502][2502];

void init() {
    memset(graph, 0, sizeof(graph));

    cin >> n >> m;
    ans = 0;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            int temp;
            cin >> temp;
            board[i][j] = temp;
            ans += temp;
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if ((i + j) % 2 == 0) {
                graph[0][m * i + j + 1] = board[i][j];
                if (i > 0) graph[m * i + j + 1][m * (i - 1) + j + 1] = max(board[i][j], board[i - 1][j]);
                if (i < n - 1) graph[m * i + j + 1][m * (i + 1) + j + 1] = max(board[i][j], board[i + 1][j]);
                if (j > 0) graph[m * i + j + 1][m * i + j] = max(board[i][j], board[i][j - 1]);
                if (j < m - 1) graph[m * i + j + 1][m * i + j + 2] = max(board[i][j], board[i][j + 1]);
            }
            else graph[m * i + j + 1][n * m + 1] = board[i][j];
        }
    }
}

void solve() {
    while (true) {
        int res, prev[n * m + 2];
        fill_n(prev, n * m + 2, -1);
        queue<pii> Q;
        Q.push(pii(0, 1000));

        while (Q.size()) {
            pii edge = Q.front();
            int cur = edge.first;
            res = edge.second;
            Q.pop();

            if (!cur) {
                for (int x = 0; x < n; x++) {
                    for (int y = x % 2; y < m; y += 2) {
                        int nxt = m * x + y + 1;
                        if (prev[nxt] == -1 && graph[cur][nxt]) {
                            prev[nxt] = cur;
                            Q.push(pii(nxt, min(res, graph[cur][nxt])));
                        }
                    }
                }
            }
            else {
                if (graph[cur][n * m + 1]) {
                    res = min(res, graph[cur][n * m + 1]);
                    prev[n * m + 1] = cur;
                    break;
                }
                int i = (cur - 1) / m;
                int j = (cur - 1) % m;

                vector<int> adj;
                if (i > 0) adj.push_back(m * (i - 1) + j + 1);
                if (i < n - 1) adj.push_back(m * (i + 1) + j + 1);
                if (j > 0) adj.push_back(m * i + j);
                if (j < m - 1) adj.push_back(m * i + j + 2);

                for (int nxt : adj) {
                    if (prev[nxt] == -1 && graph[cur][nxt]) {
                        prev[nxt] = cur;
                        Q.push(pii(nxt, min(res, graph[cur][nxt])));
                    }
                }
            }
            if (prev[n * m + 1] != -1) break;
        }
        if (prev[n * m + 1] == -1) break;

        ans -= res;
        int nxt = n * m + 1;
        while (nxt) {
            int cur = prev[nxt];
            graph[cur][nxt] -= res;
            graph[nxt][cur] += res;
            nxt = cur;
        }
    }
    cout << ans << '\n';
}

int main() {
    cin.tie(NULL); cin.tie(NULL);
    ios::sync_with_stdio(false);

    int t;
    cin >> t;
    while (t--) {
        init();
        solve();
    }
}


파이썬으로 TLE를 뚫을 수 있을지 모르겠다.

파이썬으로 AC를 받은 사람들도 존재하나, 아직은 모르는 디닉 알고리즘 (Dinic’s Algorithm)이 더 빠르다고 한다.

그래서 C++로 짜봤다.

여전히 TLE가 떴던 건 최적화가 부족해서였다.

bfs 과정에서 연결된 엣지들만을 대상으로 탐색해야 했다.

WA를 받았던 건, graph 배열을 초기화하지 않아서였다.

C++은 파이썬보다 선언, 초기화 이런 것들이 더욱 중요한 것 같다.

Leave a comment