Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

dh-winternagi 님의 블로그

(21725) 더치페이 본문

백준 (C++)/Solve

(21725) 더치페이

dh-winternagi 2026. 4. 23. 23:13

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

단계별로 풀어보기

43단계(유니온 파인드 2) 7번째

 

 

 

x가 c원을 냈다면 x가 속한 그룹의 수를 d라고 할 때 x는 c-c/d원을 더 냈고 나머지 그룹원은 c/d원을 덜 냈다. 이것을 가중치로 생각하고 이전 문제처럼 풀 수도 있지만 굉장히 까다롭고, 정석은 DSU Tree를 쓰는 것이다.

DSU Tree는 일반적인 유니온 파인드와 비슷하지만 병합 과정에서 두 정점이 다른 그룹이라면 한쪽으로 합치는 대신 새로운 정점을 만들어 두 정점을 이 새로운 정점에 이어주는 방식이다.

 

나머지 그룹원이 c/d원을 덜 낸 것을 쿼리마다 갱신하면 O(N^2)로 시간초과가 날 것이다. 따라서 그룹의 루트 정점에 c/d를 기록해 두고 나중에 역추적으로 일괄 계산하면 된다. 만약 두 그룹이 합쳐진다면 각 그룹의 루트 정점에 기록된 금액은 남아있으면서 새로운 그룹에서 지출 쿼리가 생긴다면 두 그룹 다 적용해줘야 하는데, 이때 DSU Tree를 쓴다. 병합할 때 만들어진 새로운 루트 정점에 기록하면 이전 지출 기록을 보존하면서 새로운 그룹의 지출을 쉽게 관리할 수 있다. 또한 돈을 낸 사람은 -c원의 내야 할 금액(c원의 받아야 할 금액)이 있는 것이나 마찬가지이므로 정점이 개인적으로 가진 값을 따로 기록하여 거기 저장한다(이 문제는 1~N번 노드에만 로컬 값이 들어가므로 하나의 변수로 관리해도 상관없지만, 기본적으로 DSU Tree는 갱신을 위한 값과 로컬 값을 따로 저장하는 것이 정석이라고 함). 값을 갱신하는 부분 또한 DFS로 처리하는 것이 정석이지만 이 문제는 항상 번호가 큰 정점이 상위 노드이므로 역방향으로 for문을 돌려도 된다.

 

이렇게 계산하면 마지막에 모두의 필요 금액 cost(i)가 나오는데(양수라면 내야 하고 음수라면 받아야 함) cost(i)의 총합은 0이므로 1번을 기준으로 상대적인 cost(i)값을 재설정하고 2~N번 사람은 1번하고만 돈을 주고받으면 많아야 N-1번의 송금으로 모든 정산을 할 수 있다.

사실 이 문제의 채무 관계는 제로섬이고 두 사람이 송금을 주고받을 때마다 적어도 한 명의 채무기록은 없앨 수 있으므로 항상 N-1번 이하의 송금으로 모든 계산을 마칠 수 있다. 따라서 불가능해서 -1을 출력하는 경우는 없다.

 

 

 

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  
  int n, m, ans= 0;
  
  cin >> n >> m;
  
  vector<int> uf(n+1, -1); // 음수는 집합 크기(루트), 양수는 부모 정점
  vector<pair<long, long>> cost(n+1);
  vector child(n+1, vector<int> ());
  
  auto find= [&](auto self, int x) -> int {
    if(uf[x]<0)  return x;
    else  return uf[x]= self(self, uf[x]);
  };
  
  auto merge= [&](int x, int y){
    x= find(find, x);
    y= find(find, y);
    
    if(x==y)  return;
    
    int idx= uf.size();
    
    uf.push_back(uf[x]+uf[y]);
    cost.push_back({0, 0});
    child.push_back(vector<int> ());
    child[idx].push_back(x);
    child[idx].push_back(y);
    uf[x]= idx;
    uf[y]= idx;
  };
  
  while(m--){
    int t, x, y;
    
    cin >> t >> x >> y;
    
    if(t==1){
      merge(x, y);
    }else{
      cost[x].first-= y;
      
      x= find(find, x);
      cost[x].second+= y/-uf[x];
    }
  }
  
  for(int i=uf.size()-1;i>=1;i--){
    for(int elem:child[i]){
      cost[elem].second+= cost[i].second;
    }
  }
  
  int cnt= 0;
  cost[1].first+= cost[1].second;
  for(int i=2;i<=n;i++){
    cost[i].first+= cost[i].second;
    cnt+= cost[i].first!=0;
  }
  
  cout << cnt << "\n";
  for(int i=2;i<=n;i++){
    if(!cost[i].first)  continue;
    
    if(cost[i].first>0)  cout << i << " 1 " << cost[i].first << "\n";
    else  cout << "1 " << i << " " << -cost[i].first << "\n";
  }
  
  return 0;
}

'백준 (C++) > Solve' 카테고리의 다른 글

(11505) 구간 곱 구하기  (0) 2026.04.24
(2042) 구간 합 구하기  (0) 2026.04.24
(3830) 교수님은 기다리지 않는다  (0) 2026.04.23
(28121) 산책과 쿼리  (0) 2026.04.23
(1765) 닭싸움 팀 정하기  (0) 2026.04.23