네트워크의 구조와 상관없이 몇 명이 존재하는 지만 확인하면 되기 때문에 서로소 집합(Disjoint Set)으로 해결할 수 있다.
사용자의 아이디가 대소문자의 최대 20자라길래 $(26+26)^{20}$ 이면 long long으로 변환하면 편하겠거니 했는 데, 서로소 집합에서 번호를 나타내려면 이름마다 번호를 하나씩 붙이는 게 나을 것 같았다.
네트워크를 만들고 합치는 것은 간단하다. root배열을 각 원소들이 속해있는 집합의 번호라고 했을 때 p번과 q번을 합치려면 아래와 같다.
int root[100001]; int find(int k){ if(k == root[k]) return k; return root[k] = find(root[k]); } void merge(int p, int q){ p = find(p); q = find(q); if(p != q) root[q] = p; }네트워크에 속해있는 원소들의 수는 어떻게 세야할까. 위 코드대로라면 원소는 대충 아래와 같이 있을 것이다.
[그림 1]
화살표로 가리키는 것은 부모-자식의 관계가 아니라 집합의 번호이다. 위처럼 두 집합은 초록색 원소가 그 집합의 대표라고 할 수 있다. 그럼 집합의 개수는 초록색 원소 위에 있는 숫자라고 하자.
(q가 p에 흡수되는) 위 코드를 참고하여 두 집합을 합치면 [그림 2]와 같이 나타낼 수 있다.
[그림 2]
q의 개수를 p에 더해주고, q는 1개(나중에 다른 집합으로 옮겨질 것을 우려한다면)로 바꿔주면 된다.
나머지 원소들의 집합 번호를 바꾸지 않아도 되는 것은, 나중에 조회할 때 find(e) 를 해주면 다시 갱신되기 때문이다.
서로 다른 이름이 N개의 줄마다 2개씩 나올 수 있으므로 배열의 범위는 $2N$으로 설정했다.
#include <bits/stdc++.h> using namespace std; typedef long long lld; int root[2000001]; int counter[2000001]; int find(int k){ if(k == root[k]) return k; return root[k] = find(root[k]); } int merge(int p, int q){ p = find(p); q = find(q); if(p != q){ root[q] = p; counter[p] += counter[q]; counter[q] = 1; } return counter[p]; } int main(){ int T; scanf("%d", &T); while(T--){ int n, idx1, idx2, num=1; scanf("%d ", &n); for(int i=1; i<=2*n; ++i){ root[i] = i; counter[i] = 1; } map<string, int> index; char s[21], p[21]; for(int i=0; i<n; ++i){ scanf("%s %s", s, p); if(index.count(s)==0) index[s] = num++; idx1 = index[s]; if(index.count(p)==0) index[p] = num++; idx2 = index[p]; printf("%d\n", merge(idx1, idx2)); } } return 0; }
댓글 없음:
댓글 쓰기