본문 바로가기

알고리즘/백준 문제 풀이

[C++] 27723번 Karl's shopping

728x90

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


 

26/01/12

 

 

84주차 랜덤 마라톤 H번으로 나왔던 문제다. 최대유량 문제이다.


 

문제 접근 방식:

 

 

문제에서 주어진 내용은 다음과 같다.

 

물품이 $N$개 주어져있고, 물품을 결제할 수 있는 수단(상품권, 쿠폰 등등, 편의상 바우처라고 하겠음)이 $M$개가 주어져 있음.

 

또한, 바우처의 금액(예를 들자면 상품권 금액)이 $B_{i}$로 주어지고, 물품의 가격이 $A_{i}$로 주어짐.

 

그리고 각 바우처가 어떤 물품을 살 수 있는지의 정보 또한 주어짐.

 

그랬을 때, 물품을 모두 살 때 드는 "현금"의 최소 비용을 문제에서는 묻고있다.

 

 

물품을 모두 살 때 드는 현금의 최소 비용은 (물건 금액의 총 합) - (바우처를 최대한 사용했을 때의 금액)과 동일하다.

 

바우처의 최대 비용은 최대 유량으로 모델링 할 수 있다.

 

왼쪽 정점들은 물건, 오른쪽 정점들은 바우처이며, Source와 Sink를 각각 물건과 바우처에 잇고 바우처와 물건 사이의 용량은 INF로, 나머지 간선의 용량들은 각각 $A_{i}, B_{i}$로 설정하면 된다.

 

이렇게 모델링 한 후에 유량 알고리즘을 돌리면 문제를 해결할 수 있다.


아래는 내가 위의 접근 방식과 같이 작성한 C++ 코드이다. 더보기를 누르면 확인할 수 있다.

더보기
// 27723번 Karl's shopping
// 최대 유량
#include <iostream>
#include <vector>
#include <queue>

using namespace std;
#define fastio ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define endl '\n'
#define INF 2147483647

struct Edge{
    int target, capacity, flow; // 타겟, 용량, 유량
    Edge* reverse; // 역방향 간선 참조하는 포인터
    int residualCapacity() const { // 잔여 유량 계산
        return capacity - flow;
    }
    void push(int amount){ // 유량 흘리기(갱신)
        flow += amount;
        reverse->flow -= amount;
    }
};

void addEdge(int u, int v, int capacity, vector<vector<Edge*>>& graph){
    Edge* uv = new Edge(); // u에서 v로 가는 간선 포인터
    Edge* vu = new Edge(); // v에서 u로 가는 역방향 간선 포인터
    // 정방향 간선 uv 초기화
    uv->target = v;
    uv->capacity = capacity;
    uv->flow = 0;
    uv->reverse = vu;
    // 역방향 간선 vu 초기화
    vu->target = u;
    vu->capacity = 0;
    vu->flow = 0;
    vu->reverse = uv;
    // 정방향과 역방향 둘다 넣어준다.
    graph[u].push_back(uv);
    graph[v].push_back(vu);
}

int BFS(int source, int sink, vector<Edge*>& reversedPath, vector<vector<Edge*>>& graph){
    int N = graph.size();
    vector<int> visited(N, 0);
    vector<Edge*> parent(N); // 경로 역추적하기 위한 parent배열 선언.
    queue<int> Q;
    visited[source] = 1;
    Q.push(source);
    
    bool foundAugmentingPath = false;
    while (!Q.empty() && !foundAugmentingPath){
        int now = Q.front(); Q.pop();
        for (Edge* edge: graph[now]){
            int next = edge->target;
            if (!visited[next] && edge->residualCapacity() > 0){ // 방문하지 않았고, 잔여 용량이 남아있다면
                visited[next] = 1;
                parent[next] = edge;
                Q.push(next);
                if (next == sink){ // sink로 도달가능하다면
                    foundAugmentingPath = true;
                    break;
                }
            }
        }
    }
    
    if (!foundAugmentingPath){
        return 0; // 더 이상 흘릴 유량이 없다.
    }

    reversedPath.clear();
    int Flow = INF;
    // 역방향 간선타고 가면서 흘릴 Flow 갱신
    for (Edge* edge = parent[sink]; edge != nullptr; edge = parent[edge->reverse->target]){
        Flow = min(Flow, edge->residualCapacity());
        reversedPath.push_back(edge);
    }
    return Flow;
}

int EdmondKarp(int source, int sink, vector<vector<Edge*>>& graph){
    int totalFlow = 0;
    vector<Edge*> reversedPath;
    while (int flow = BFS(source, sink, reversedPath, graph)){
        for (Edge* edge : reversedPath){
            edge->push(flow);
        }
        totalFlow += flow;
    }
    return totalFlow;
}

void solve(void){
    int N, M; cin >> N >> M;
    vector<vector<Edge*>> graph(N+M+2);
    vector<int> A(N), B(M);
    int totalCash = 0;
    for (int i = 0; i < N; ++i){
        cin >> A[i];
        totalCash += A[i];
        addEdge(0, i+1, A[i], graph);
    }
    for (int i = 0; i < M; ++i){
        cin >> B[i];
        addEdge(N+i+1, N+M+1, B[i], graph);
    }
    for (int i = 0; i < M; ++i){
        int K; cin >> K;
        int num;
        for (int j = 0; j < K; ++j){
            cin >> num;
            addEdge(num, N+i+1, INF, graph);
        }
    }
    cout << totalCash - EdmondKarp(0, N+M+1, graph) << endl;
}

int main(void){
    fastio

    int T; cin >> T;
    for (int i = 0; i < T; ++i){
        solve();
    }
    return 0;
}
728x90

'알고리즘 > 백준 문제 풀이' 카테고리의 다른 글

[C++] 14398번 피타고라스 수  (0) 2025.12.26
[C++] 16590번 KMP  (0) 2025.12.24
[C++] 6786번 Blood Distribution  (0) 2025.12.23
[C++] 22695번 Spirograph  (0) 2025.11.08
[C++] 24558번 Downsizing  (0) 2025.10.21