1월27일 - 정리정돈[BOJ/12918]

Yullgiii·2025년 1월 26일
0

문제 설명

철수는 방을 정리하려고 한다. 방을 정확히 절반으로 나누는 y축 선대칭으로 물건들이 정렬되도록 배치하고자 한다. 물건의 위치와 대칭 위치 간 이동 거리는 유클리드 거리로 계산되며, 물건들이 이동하는 거리의 합을 최소화해야 한다.

문제의 핵심은 다음과 같다:

  • 물건들의 위치는 2차원 좌표로 주어진다.
  • 각 물건은 반드시 대칭된 위치에 존재해야 하며, 이 위치에 도달하기 위해 이동 거리를 최소화해야 한다.

해결 방법

이 문제는 이분 매칭최소 비용 최대 유량(MCMF) 알고리즘을 활용하여 해결한다.

  1. 이분 매칭 모델링

    • 주어진 물건들의 위치를 기준으로 대칭 위치를 생성한다.
    • 실제 물건들과 대칭된 물건들 간 매칭 문제로 변환한다.
  2. 최소 비용 최대 유량

    • 각 물건을 소스와 연결하고, 대칭 위치를 싱크와 연결한다.
    • 물건과 대칭 위치 간의 비용은 해당 두 좌표 사이의 유클리드 거리이다.
    • 최소 비용으로 모든 물건을 대칭 위치에 매칭한다.
  3. 거리 계산

    • 두 점 사이의 거리는 다음 공식을 사용한다:
      [
      D = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}
      ]

코드

#include <bits/stdc++.h>
#define ll long long
#define ld long double
#define loop(i, start, end) for (int i = (start); i < (end); i++)
using namespace std;
typedef pair<int, int> Point;

void setupFastIO() {
    cin.tie(0);
    cout.tie(0);
    ios_base::sync_with_stdio(false);
}

const int MAX_SIZE = 202;
int objectCount;
Point objects[100];

// Minimum Cost Maximum Flow 구조체
struct MinCostMaxFlow {
    struct Edge {
        int destination, capacity, reverseIndex;
        double cost;

        Edge(int dest, int cap, int rev, double cost)
            : destination(dest), capacity(cap), reverseIndex(rev), cost(cost) {}
    };

    vector<Edge> edges[MAX_SIZE];
    int source, sink;

    void initialize() {
        loop(i, 0, MAX_SIZE) edges[i].clear();
    }

    void addEdge(int from, int to, int cap, double cost) {
        edges[from].emplace_back(to, cap, edges[to].size(), cost);
        edges[to].emplace_back(from, 0, edges[from].size() - 1, -cost);
    }

    void setSourceSink(int src, int snk) {
        source = src;
        sink = snk;
    }

    double distances[MAX_SIZE];
    int parentNode[MAX_SIZE], parentEdge[MAX_SIZE];
    bool inQueue[MAX_SIZE];

    bool shortestPath() {
        fill(distances, distances + MAX_SIZE, 1e9);
        fill(inQueue, inQueue + MAX_SIZE, false);

        queue<int> q;
        q.push(source);
        distances[source] = 0;
        inQueue[source] = true;
        while (!q.empty()) {
            int current = q.front();
            q.pop();
            inQueue[current] = false;
            for (int i = 0; i < edges[current].size(); i++) {
                int next = edges[current][i].destination;
                int cap = edges[current][i].capacity;
                double cost = edges[current][i].cost;
                if (cap > 0 && distances[next] > distances[current] + cost + 1e-9) {
                    distances[next] = distances[current] + cost;
                    parentNode[next] = current;
                    parentEdge[next] = i;
                    if (!inQueue[next]) {
                        q.push(next);
                        inQueue[next] = true;
                    }
                }
            }
        }
        return distances[sink] != 1e9;
    }

    double findMatching() {
        double result = 0;
        loop(i, 0, objectCount) {
            shortestPath();
            result += distances[sink];
            for (int j = sink; j != source; j = parentNode[j]) {
                int reverseIdx = parentEdge[j];
                edges[parentNode[j]][reverseIdx].capacity--;
                edges[j][edges[parentNode[j]][reverseIdx].reverseIndex].capacity++;
            }
        }
        return result;
    }
} FlowSolver;

// 두 점 사이의 거리 계산
double calculateDistance(Point a, Point b) {
    double dx = (double)a.first - b.first;
    double dy = (double)a.second - b.second;
    return sqrt(dx * dx + dy * dy);
}

void solve() {
    cin >> objectCount;
    loop(i, 0, objectCount) cin >> objects[i].first >> objects[i].second;

    FlowSolver.initialize();
    FlowSolver.setSourceSink(objectCount * 2, objectCount * 2 + 1);

    loop(i, 0, objectCount) {
        FlowSolver.addEdge(FlowSolver.source, i * 2, 1, 0);
        FlowSolver.addEdge(i * 2 + 1, FlowSolver.sink, 1, 0);
    }

    loop(i, 0, objectCount) {
        loop(j, 0, objectCount) {
            if (i == j) continue;
            FlowSolver.addEdge(i * 2, j * 2 + 1, 1, calculateDistance(objects[i], objects[j]));
        }
    }

    cout << fixed << setprecision(3) << FlowSolver.findMatching() << '\n';
}

int main() {
    setupFastIO();
    solve();
    return 0;
}

코드 설명

주요 로직

  1. 입력 처리

    • 물건들의 좌표를 입력받아 저장한다.
  2. MCMF 초기화

    • 모든 물건을 소스 노드와 연결하고, 매칭 위치를 싱크와 연결한다.
    • 각 물건과 매칭 위치 간의 비용은 거리로 설정한다.
  3. 최소 비용 계산

    • shortestPath 함수로 최소 비용을 계산한다.
    • 플로우를 증가시키며 총 비용을 누적한다.
  4. 거리 계산

    • 최종적으로 물건들을 정렬한 후 최소 비용을 출력한다.

So...

이 문제는 최소 비용 최대 유량이분 매칭 알고리즘의 조합을 학습할 수 있는 좋은 예제다. 물건들을 대칭으로 정렬하는 문제를 그래프 문제로 변환하는 과정에서 흥미로웠고, 특히 유클리드 거리 계산과 비용 최적화 부분에서 많은 고민을 했다. 이러한 접근법은 다른 최적화 문제에도 응용 가능하다.

profile
공부하는 거북이의 성장일기 🐢

0개의 댓글