알고리즘 문제해결/BOJ

BOJ 25402 트리와 쿼리

havana723 2022. 8. 31. 18:06

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

 

25402번: 트리와 쿼리

첫 번째 줄부터 QQ개의 줄에 걸쳐, 각 질의에 대한 답을 출력한다. 이 중 ii (1iQ1iQ)번째 줄에는 ii번째 질의에서 주어진 SS에 대하여, SS의 연결 강도를 출력한다.

www.acmicpc.net

 

매 쿼리마다 SS가 주어졌을 때 SS 안의 정점들 주 연결되어 있는 정점 쌍의 개수를 찾는 문제입니다.

 

문제를 정리하면, SS가 주어질 때 SS안의 정점들이 몇 개의 컴포넌트로 나누어지는지, 그리고 각 컴포넌트에는 몇 개의 정점이 있는지 찾는 문제라고도 할 수 있습니다. 컴포넌트 내의 정점들은 모두 서로 연결되어 있다고 할 수 있으므로 매 컴포넌트마다 컴포넌트 안의 정점에서 두 개를 뽑는 경우의 수를 전부 더해주면 되기 때문입니다.

 

그렇다면 컴포넌트들을 어떻게 구할 수 있을까요? 나이브하게 생각해보면 매 쿼리당 dfs를 돌려서 구할 수 있습니다. 하지만 이 방법은 모든 간선을 확인해야 하기 때문에 쿼리당 O(n)O(n)의 시간이 필요합니다. 시간을 줄이기 위해서는 컴포넌트에 속한 간선들만 따로 구할 수 있어야 합니다. 컴포넌트에 속한 간선들로만 새로 그래프를 만들어 dfs를 돌리면  O(k)O(k)의 시간에 컴포넌트를 구할 수 있기 때문입니다.

 

컴포넌트에 속한 간선을 구하는 것은 나이브하게 생각해보면  O(k2)O(k2)의 시간이 걸립니다. SS내의 모든 정점 쌍에 대해 두 정점을 잇는 간선이 존재한다면 해당 간선이 컴포넌트에 속한다고 할 수 있기 때문입니다. 이를 최적화하기 위해 트리의 성질을 이용할 수 있습니다.

 

임의의 정점을 루트로 잡아 트리를 구성한다고 할 때, 정점 aa와 정점 bb가 이어져 있다는 것은 정점 aa와 정점 parent(a)parent(a) 또는 정점 bb와 정점 parent(b)parent(b)가 연결되어 있다고 뜻입니다. 트리의 모든 간선은 부모 자식 관계의 정점들만을 이시습니다. 달리 말해 SS안의 정점 xx에 대하여 컴포넌트 내의 간선으로 사용될 수 있는 후보는 xxparent(x)parent(x)를 잇는 간선 뿐입니다. 따라서 SS안의 모든 정점에 대해 자식과 부모가 모두 SS안에 있는 경우의 간선들만 추가한 새로운 그래프를 만들고, 해당 그래프에서 dfs를 돌리면 답을 구할 수 있습니다.

 

아래는 코드입니다.

 

#include <bits/stdc++.h>
#define FastIO ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
#define ends ' '
#define int long long
#define pii pair<int,int>

using namespace std;

int T, n, q, visited[250010], parent[250010], inS[250010];
vector<int> S;
vector<vector<int>> graph;
vector<vector<int>> newGraph;

int calc(int x) {
  return (x * (x - 1)) / 2;
}

int dfs(int x) {
  visited[x]++;
  int ret = 1;
  for (auto next : newGraph[x]) {
    if (visited[next]) continue;
    ret += dfs(next);
  }
  return ret;
}

void makeTree(int x) {
  visited[x]++;
  for (auto next : graph[x]) {
    if (visited[next]) continue;
    parent[next] = x;
    makeTree(next);
  }
}

int32_t main(void) {
  FastIO;
  T = 1;
  //cin >> T;

  while (T--) {
    cin >> n;

    graph.resize(n + 1);

    for (int i = 1; i < n; i++) {
      int a, b;
      cin >> a >> b;
      graph[a].push_back(b);
      graph[b].push_back(a);
    }

    makeTree(1);
    memset(visited, 0, sizeof(visited));

    cin >> q;

    while (q--) {
      int k;

      cin >> k;

      S.clear();
      S.resize(k);
      newGraph.clear();
      newGraph.resize(k + 1);

      for (int i = 0; i < k; i++) {
        cin >> S[i];
        inS[S[i]] = i + 1;
      }

      for (auto x : S) {
        if (inS[parent[x]]) {
          newGraph[inS[x]].push_back(inS[parent[x]]);
          newGraph[inS[parent[x]]].push_back(inS[x]);
        }
      }

      int ans = 0;

      for (int i = 0; i < k; i++) {
        if (visited[i]) continue;
        ans += calc(dfs(i));
      }

      cout << ans << endl;

      for (int i = 1; i <= k; i++) visited[i] = 0;
      for (auto x : S) inS[x] = 0;
    }
  }

  return 0;
}

'알고리즘 문제해결 > BOJ' 카테고리의 다른 글

BOJ 20938 반짝반짝  (0) 2022.09.02
BOJ 17308 Getting-Up Syndrome  (0) 2022.09.01
BOJ 23090 난민  (0) 2022.08.30
BOJ 24456 초콜릿 훔쳐 먹기  (0) 2022.08.29
BOJ 24914 Split the SSHS  (0) 2022.08.29