RiseOJ는 solved.ac와 제휴 관계가 없습니다. 티어 아이콘 © solved.ac. solved.ac
포럼
A00008

알파카컵 1회: H - 알파카의 컵 보관소

Diamond IV 다이아몬드 IV
난이도
1s
시간 제한
256MB
메모리 제한
3
맞았습니다!!
9
제출 수
33.3%
정답률
레이팅

의견: 0

설명

알파카 컵 대회장에는 \(N\)개의 장소가 있고, 장소들은 \(N-1\)개의 양방향 길로 연결되어 있다.

모든 장소는 서로 이동 가능하며, 길들은 트리 구조를 이룬다.

각 길에는 이동하는 데 걸리는 시간이 정해져 있다.

처음에는 모든 장소에 컵 보관소가 없다.

이후 \(Q\)개의 쿼리가 주어진다. 쿼리는 두 종류이다.

  • 1 x : 장소 \(x\)의 컵 보관소 상태를 바꾼다. 보관소가 없으면 만들고, 있으면 없앤다.
  • 2 x : 장소 \(x\)에서 가장 가까운 컵 보관소까지의 최단 거리를 출력한다.

만약 현재 존재하는 컵 보관소가 하나도 없다면 \(-1\)을 출력한다.

제약

\(1 \le N \le 200{,}000\)
\(1 \le Q \le 200{,}000\)
\(1 \le a, b, x \le N\)
\(1 \le w \le 1{,}000{,}000{,}000\)
주어지는 그래프는 트리이다.
정답은 int 범위를 넘을 수 있으므로 64비트 정수를 사용해야 한다.

입력 형식

첫째 줄에 장소의 수 \(N\)과 쿼리의 수 \(Q\)가 공백으로 구분되어 주어진다.

다음 \(N-1\)개의 줄에는 길의 정보 \(a\), \(b\), \(w\)가 주어진다. 이는 장소 \(a\)와 장소 \(b\)를 잇는 길의 이동 시간이 \(w\)임을 의미한다.

다음 \(Q\)개의 줄에는 쿼리가 한 줄에 하나씩 주어진다. 각 쿼리는 다음 두 형태 중 하나이다.

  • 1 x : 장소 \(x\)의 컵 보관소 상태를 바꾼다. (없으면 만들고, 있으면 없앤다.)
  • 2 x : 장소 \(x\)에서 가장 가까운 컵 보관소까지의 최단 거리를 출력한다. 보관소가 하나도 없다면 \(-1\)을 출력한다.
출력 형식

2 x 쿼리가 주어질 때마다 정답을 한 줄에 하나씩 출력한다.

예제 1
입력
5 9
1 2 3
1 3 2
2 4 4
2 5 1
2 1
1 4
2 5
1 3
2 5
2 1
1 4
2 5
1 3
출력
-1
5
5
2
6
설명

초기에는 컵 보관소가 없으므로 첫 번째 질의의 답은 -1이다.

이후 장소 4에 컵 보관소가 생긴다.

장소 5에서 가장 가까운 컵 보관소는 장소 4이고, 거리는

5 → 2 → 4 = 1 + 4 = 5

이다.

그 후 장소 3에도 컵 보관소가 생긴다.

장소 1에서 가장 가까운 컵 보관소는 장소 3이고, 거리는 2이다.

예제 2
입력
3 6
1 2 10
2 3 1
1 1
2 3
1 3
2 2
1 3
2 2
출력
11
1
10
문제 정보

anthony0506 작성

출처 알파카컵 1회

해설

알파카컵 1회: H - 알파카의 컵 보관소

알파카컵 1회: H - 알파카의 컵 보관소

1. 문제 요약

\(N\)개의 정점과 \(N-1\)개의 가중치 있는 간선으로 이루어진 트리가 주어집니다. 각 간선에는 이동 시간(가중치) \(w\)가 있습니다. 처음에는 어느 정점에도 컵 보관소가 없는 상태에서, \(Q\)개의 쿼리를 순서대로 처리해야 합니다.

  • 1 x : 정점 \(x\)의 보관소 상태를 토글합니다. 보관소가 없으면 새로 만들고, 있으면 없앱니다.
  • 2 x : 정점 \(x\)에서 현재 보관소가 있는 정점들 중 가장 가까운 곳까지의 최단 거리를 출력합니다. 보관소가 하나도 없으면 \(-1\)을 출력합니다.

즉, 동적으로 변하는 정점 집합 \(S\)(보관소가 있는 정점들의 집합)에 대해, 매 질의마다
$$ \text{answer}(x) = \min_{s \in S} \text{dist}(x, s) $$
를 구하는 문제입니다. (\(S\)가 비어 있으면 \(-1\).) 여기서 \(\text{dist}(u, v)\)는 트리에서 두 정점을 잇는 유일한 경로의 가중치 합입니다.

가중치 \(w\)가 최대 \(10^9\)이고 경로에 최대 \(N-1\)개의 간선이 놓일 수 있으므로, 거리는 최대 약 \(2 \times 10^{14}\)까지 커집니다. 반드시 64비트 정수(long long)를 사용해야 합니다.

입력 형식은 다음과 같습니다. 첫 줄에 \(N\ Q\)가 주어지고, 이어지는 \(N-1\)개의 줄에 간선 정보 \(a\ b\ w\)(정점 \(a\)\(b\)를 잇는 가중치 \(w\)의 간선)가 주어집니다. 그 뒤 \(Q\)개의 줄에 위의 두 종류 쿼리가 주어집니다.

2. [부분 풀이 1]: 매 질의마다 직접 탐색

가장 직관적인 접근입니다. 보관소의 집합 \(S\)bool 배열로 관리하고, 2 x 질의가 들어올 때마다 정점 \(x\)에서 출발하여 트리 전체를 탐색해 가장 가까운 보관소를 직접 찾습니다.

트리에는 음수 간선이 없고, 사실 사이클도 없으므로 단순 BFS/DFS로 모든 정점까지의 거리를 구해도 되지만, 일반적인 가중치 그래프 사고에 익숙해지기 위해 다익스트라(우선순위 큐)로 서술합니다. \(x\)에서 가까운 정점부터 거리를 확정해 나가다가, 처음으로 보관소가 있는 정점을 만나는 순간이 곧 정답입니다. 보관소를 끝까지 만나지 못하면 \(-1\)입니다.

  • 풀이: 1 x 쿼리는 mark[x]를 토글하는 것으로 \(O(1)\)에 처리합니다. 2 x 쿼리는 \(x\)를 시작점으로 다익스트라(트리이므로 BFS여도 무방)를 돌려, 거리가 확정되는 정점이 보관소이면 즉시 그 거리를 답으로 출력하고 종료합니다. 큐가 빌 때까지 보관소를 못 찾으면 \(-1\)을 출력합니다.

  • 시간 복잡도: 질의 한 번에 트리 전체를 보므로 \(O(N \log N)\)(또는 BFS로 \(O(N)\))이 걸립니다. 전체 \(O(Q \cdot N \log N)\)로, \(N, Q \le 2 \times 10^5\)에서는 \(10^{10}\)을 한참 넘어 시간 초과입니다. 작은 입력에서 정답을 검증하는 용도로만 유효합니다.

#include <bits/stdc++.h>
#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define endl "\n"
using namespace std;
using ll = long long;
using pli = pair<ll, int>;
int n, q;
vector<pli> v[200001];
bool mark[200001];
int main() {
    fastio
    cin >> n >> q;
    for (int i = 1; i < n; i++) {
        int a, b; ll w;
        cin >> a >> b >> w;
        v[a].push_back({w, b});
        v[b].push_back({w, a});
    }
    while (q--) {
        int t, x;
        cin >> t >> x;
        if (t == 1) {
            mark[x] = !mark[x];
        } else {
            vector<ll> dist(n + 1, LLONG_MAX);
            priority_queue<pli, vector<pli>, greater<pli>> pq;
            dist[x] = 0;
            pq.push({0, x});
            ll ans = -1;
            while (!pq.empty()) {
                auto [d, u] = pq.top();
                pq.pop();
                if (d > dist[u]) continue;
                if (mark[u]) { ans = d; break; }
                for (auto [w, nx] : v[u]) {
                    if (dist[u] + w < dist[nx]) {
                        dist[nx] = dist[u] + w;
                        pq.push({dist[nx], nx});
                    }
                }
            }
            cout << ans << endl;
        }
    }
}

3. [부분 풀이 2]: 보관소가 적거나 질의가 적은 경우

부분 풀이 1이 느린 이유는 매 질의마다 트리 전체를 훑기 때문입니다. 한 가지 자연스러운 개선은 "거리는 항상 보관소 쪽에서 계산한다"는 관점입니다.

만약 현재 보관소의 개수 \(|S|\)가 작다면, \(x\)에서 모든 보관소까지 일일이 거리를 구하는 편이 더 빠릅니다. 트리에서 두 정점의 거리는 \(\text{LCA}\)(최소 공통 조상)를 이용해 다음과 같이 \(O(\log N)\)에 구할 수 있습니다.
$$ \text{dist}(u, v) = \text{depthSum}[u] + \text{depthSum}[v] - 2 \cdot \text{depthSum}[\text{LCA}(u, v)] $$
여기서 \(\text{depthSum}[u]\)는 루트에서 \(u\)까지의 가중치 합입니다. 희소 배열(sparse table)로 \(\text{LCA}\)를 전처리해 두면, 2 x 질의는 현재 보관소 각각에 대해 거리를 계산해 최솟값을 취하는 것으로 처리됩니다.

  • 풀이: \(\text{LCA}\)용 희소 배열과 루트로부터의 누적 거리 \(\text{depthSum}\)을 전처리합니다(1 기반 깊이). 보관소 집합 \(S\)를 따로 들고 다니며, 1 x\(S\)\(x\)를 넣거나 빼고, 2 x\(S\)의 모든 원소 \(s\)에 대해 \(\text{dist}(x, s)\)를 계산해 최솟값을 출력합니다(\(S\)가 비면 \(-1\)).

  • 시간 복잡도: 2 x 질의 한 번에 \(O(|S| \log N)\). 보관소가 매우 적거나 질의 수가 적을 때만 통과합니다. 최악의 경우 보관소가 \(O(N)\)개라면 질의당 \(O(N \log N)\)이 되어 부분 풀이 1과 다를 바 없습니다. "거리를 보관소 집합 위에서 본다"는 발상은 정해로 가는 다리가 됩니다. 핵심은 이 \(\min\) 연산을 어떻게 트리 구조를 활용해 매번 \(O(N)\)이 아니라 \(O(\text{polylog})\)로 줄이느냐입니다.

#include <bits/stdc++.h>
#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define endl "\n"
using namespace std;
using ll = long long;
const int LOG = 18;
int n, q;
vector<pair<int, ll>> v[200001];
int up[LOG][200001], dep[200001];
ll depthSum[200001];
void dfs(int x, int p) {
    up[0][x] = p;
    for (int j = 1; j < LOG; j++) up[j][x] = up[j - 1][up[j - 1][x]];
    for (auto [nx, w] : v[x]) if (nx != p) {
        dep[nx] = dep[x] + 1;
        depthSum[nx] = depthSum[x] + w;
        dfs(nx, x);
    }
}
int lca(int a, int b) {
    if (dep[a] < dep[b]) swap(a, b);
    int diff = dep[a] - dep[b];
    for (int j = LOG - 1; j >= 0; j--) if ((diff >> j) & 1) a = up[j][a];
    if (a == b) return a;
    for (int j = LOG - 1; j >= 0; j--) if (up[j][a] != up[j][b]) { a = up[j][a]; b = up[j][b]; }
    return up[0][a];
}
ll dist(int a, int b) { return depthSum[a] + depthSum[b] - 2 * depthSum[lca(a, b)]; }
set<int> S;
int main() {
    fastio
    cin >> n >> q;
    for (int i = 1; i < n; i++) {
        int a, b; ll w;
        cin >> a >> b >> w;
        v[a].push_back({b, w});
        v[b].push_back({a, w});
    }
    dep[1] = 0;
    dfs(1, 1);
    while (q--) {
        int t, x;
        cin >> t >> x;
        if (t == 1) {
            if (S.count(x)) S.erase(x);
            else S.insert(x);
        } else {
            if (S.empty()) { cout << -1 << endl; continue; }
            ll ans = LLONG_MAX;
            for (int s : S) ans = min(ans, dist(x, s));
            cout << ans << endl;
        }
    }
}

4. 정해: 센트로이드 분할 (Centroid Decomposition)

부분 풀이 2에서 본 "거리를 보관소 집합 위에서 \(\min\)으로 본다"는 관점을, 센트로이드 분할로 자료구조화하면 질의·갱신을 모두 \(O(\log^2 N)\)에 처리할 수 있습니다.

센트로이드 분할이란

트리의 센트로이드(무게중심) 는, 그 정점을 제거했을 때 분리되는 모든 서브트리의 크기가 전체의 절반 이하가 되는 정점입니다. 센트로이드를 찾아 제거하면 트리는 각각 크기가 절반 이하인 조각들로 나뉩니다. 각 조각에 대해 이 과정을 재귀적으로 반복하면, 정점들 사이에 센트로이드 트리라는 보조 트리가 만들어집니다.

이 센트로이드 트리의 핵심 성질은 높이가 \(O(\log N)\) 이라는 점입니다. 따라서 원래 트리의 어떤 정점 \(x\)를 잡아도, 센트로이드 트리에서 \(x\)의 조상(자기 자신 포함)은 \(O(\log N)\)개뿐입니다. 이 조상들을 \(x\)센트로이드 조상이라 부릅니다.

가장 중요한 사실은 다음과 같습니다.

원래 트리에서 임의의 두 정점 \(u, v\) 사이의 경로는, 센트로이드 트리에서 \(u\)\(v\)공통 센트로이드 조상 중 적어도 하나(구체적으로는 둘의 LCA가 되는 센트로이드 \(c\))를 반드시 지난다.

따라서 \(\text{dist}(u, v) = \text{dist}(u, c) + \text{dist}(c, v)\)가 그 \(c\)에서 성립합니다. 즉, 모든 경로를 "어떤 센트로이드를 경유하는 경로"로 분해해 관리할 수 있습니다.

센트로이드마다 multiset 들기

각 센트로이드 \(c\)에 대해, 그 시점에 보관소가 켜져 있고 \(c\)를 센트로이드 조상으로 갖는 정점들의, \(c\)까지의 거리들을 모아 둔 multiset(다중집합) \(\text{ms}[c]\)를 유지합니다.

  • 토글 1 x: \(x\)의 모든 센트로이드 조상 \(c\)에 대해, 보관소를 켜는 경우 \(\text{dist}(x, c)\)\(\text{ms}[c]\)에 삽입하고, 끄는 경우 그 값을 하나 제거합니다. 조상이 \(O(\log N)\)개이므로 갱신은 \(O(\log N)\)번의 multiset 연산입니다.

  • 질의 2 x: \(x\)의 각 센트로이드 조상 \(c\)에 대해 후보값 \(\text{dist}(x, c) + \min(\text{ms}[c])\)를 계산하고(단 \(\text{ms}[c]\)가 비어 있지 않을 때만), 이 후보들의 최솟값을 답으로 합니다. 모든 조상의 \(\text{ms}\)가 비어 있으면 \(-1\)입니다.

이것이 올바른 이유: 임의의 보관소 \(s\)와 질의점 \(x\) 사이의 최단 거리 경로는 어떤 공통 센트로이드 조상 \(c\)를 지나며, 그때 \(\text{dist}(x, s) = \text{dist}(x, c) + \text{dist}(c, s)\)입니다. 우리는 그 \(c\)에서 \(\text{dist}(c, s)\)\(\text{ms}[c]\)에 이미 넣어 두었으므로, \(c\)의 후보 \(\text{dist}(x, c) + \min(\text{ms}[c]) \le \text{dist}(x, c) + \text{dist}(c, s) = \text{dist}(x, s)\)로 정답을 놓치지 않습니다. 반대로, 어떤 후보값도 실제로 존재하는 어떤 보관소까지의 (경유 경로) 거리이므로 실제 최단 거리보다 작아질 수 없습니다. 따라서 후보들의 최솟값이 정확히 정답입니다. (경유 경로의 길이는 직선 경로 길이 이상이지만, 정답을 주는 \(c\)에서는 등호가 성립하므로 최솟값은 정확합니다.)

-1 처리: 모든 토글이 정확히 대칭으로 삽입/삭제되므로, 보관소가 하나도 없을 때는 모든 \(\text{ms}[c]\)가 비어 있어 어떤 후보도 만들어지지 않고 자연스럽게 \(-1\)이 출력됩니다.

dist(x, 센트로이드 조상) 빠르게 구하기

\(\text{dist}(x, c)\)가 필요한데, 매번 LCA를 호출해도 \(O(\log N)\)이라 전체 \(O(\log^2 N)\)으로 충분합니다. 다만 상수를 줄이기 위해, 분할 과정에서 각 센트로이드 \(c\)로부터 그 조각 안의 모든 정점까지의 거리를 한 번의 DFS로 미리 계산해 둘 수 있습니다. 정점 \(x\)마다 센트로이드 조상별 거리를 깊이 순서대로 배열 \(\text{distc}[x]\)에 저장해 두면, 토글·질의에서 곧바로 \(O(1)\)에 꺼내 쓸 수 있습니다. 아래 구현은 이 방식을 사용합니다.

구현 주의점:
- 분할은 반드시 현재 조각의 크기를 다시 센 뒤 센트로이드를 찾아야 합니다. (이미 제거된 정점은 removed로 건너뜁니다.)
- 센트로이드 조상까지의 거리는 \(10^{14}\)급이므로 long long으로 다룹니다.
- multiset에서 같은 값이 여러 번 들어갈 수 있으므로, 삭제는 반드시 find하나의 반복자만 지운 뒤 erase(it)해야 합니다. erase(value)는 같은 값을 전부 지우므로 사용하면 안 됩니다.
- \(N\)이 최대 \(2 \times 10^5\)이고 트리가 일자(path) 형태일 수 있으므로, 크기 계산·센트로이드 탐색·거리 수집을 재귀 DFS로 짜면 깊이가 \(2 \times 10^5\)에 달해 스택 오버플로(런타임 에러) 가 납니다. 아래 구현은 이 모든 과정을 명시적 스택/큐 기반의 반복(iterative) DFS로 작성해 안전하게 처리합니다. (또는 컴파일러·채점 환경에서 스택 크기를 충분히 키워야 합니다.)

  • 풀이: 위 설명대로, (1) 센트로이드 분할로 센트로이드 트리(부모 cpar, 깊이 cdep)와 각 정점의 센트로이드 조상별 거리 distc를 전처리하고, (2) 1 x\(x\)의 모든 센트로이드 조상의 multiset에 거리를 삽입/삭제, (3) 2 x는 그 조상들에서 (거리 + multiset 최솟값)의 최솟값을 출력합니다.

  • 시간 복잡도: 분할 전처리는 각 정점이 \(O(\log N)\)개의 조각에 속하므로 \(O(N \log N)\)입니다. 토글·질의는 모두 \(O(\log N)\)개의 센트로이드 조상을 순회하며 각각 \(O(\log N)\)의 multiset 연산(또는 \(O(1)\)의 거리 조회 + 최솟값 조회)을 하므로 쿼리당 \(O(\log^2 N)\)입니다. 전체 \(O((N + Q) \log^2 N)\)로 제한 안에 통과합니다.

#include <bits/stdc++.h>
#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define endl "\n"
using namespace std;
using ll = long long;
const int MAXN = 200005;
int n, q;
vector<pair<int, ll>> v[MAXN];
bool removed[MAXN];
int sz[MAXN], par_[MAXN];
int cpar[MAXN];          // 센트로이드 트리에서의 부모 (루트는 -1)
int cdep[MAXN];          // 센트로이드 트리에서의 깊이 (루트 = 0)
vector<ll> distc[MAXN];  // distc[x][d] = (cdep가 d인 x의 센트로이드 조상)까지의 거리
multiset<ll> ms[MAXN];    // 센트로이드 c에 모인, 켜져 있는 보관소까지의 거리들
bool mark[MAXN];
int order_[MAXN], ocnt;  // 반복 DFS 방문 순서 버퍼
// 반복 DFS로 현재 조각의 크기 계산 (역순으로 누적), root 크기 반환
int buildSizes(int root) {
    ocnt = 0;
    order_[ocnt++] = root; par_[root] = -1;
    for (int i = 0; i < ocnt; i++) {
        int x = order_[i];
        for (auto [nx, w] : v[x]) if (nx != par_[x] && !removed[nx]) {
            par_[nx] = x;
            order_[ocnt++] = nx;
        }
    }
    for (int i = ocnt - 1; i >= 0; i--) {
        int x = order_[i];
        sz[x] = 1;
        for (auto [nx, w] : v[x]) if (nx != par_[x] && !removed[nx]) sz[x] += sz[nx];
    }
    return sz[root];
}
// 절반 초과 자식 쪽으로 내려가며 센트로이드를 반복적으로 탐색
int findCent(int root, int tot) {
    int x = root, p = -1;
    while (true) {
        int nxt = -1;
        for (auto [nx, w] : v[x]) if (nx != p && !removed[nx] && sz[nx] > tot / 2) { nxt = nx; break; }
        if (nxt == -1) return x;
        p = x; x = nxt;
    }
}
// 반복 DFS로 센트로이드 c에서 조각 내 모든 정점까지 거리를 distc에 추가
// (분할 순서상 조상이 항상 깊이 순으로 먼저 들어오므로, push_back 인덱스 = cdep)
void gather(int c) {
    int cnt = 0;
    order_[cnt++] = c; par_[c] = -1; distc[c].push_back(0);
    for (int i = 0; i < cnt; i++) {
        int x = order_[i];
        ll dx = distc[x].back();
        for (auto [nx, w] : v[x]) if (nx != par_[x] && !removed[nx]) {
            par_[nx] = x;
            distc[nx].push_back(dx + w);
            order_[cnt++] = nx;
        }
    }
}
// 명시적 스택 기반 센트로이드 분할
void decomposeAll() {
    stack<tuple<int, int, int>> st;  // (조각 진입점, 센트로이드 트리 부모, 깊이)
    st.push({1, -1, 0});
    while (!st.empty()) {
        auto [entry, par, dpt] = st.top(); st.pop();
        int tot = buildSizes(entry);
        int c = findCent(entry, tot);
        cpar[c] = par; cdep[c] = dpt;
        removed[c] = true;
        gather(c);
        for (auto [nx, w] : v[c]) if (!removed[nx]) st.push({nx, c, dpt + 1});
    }
}
void toggle(int x) {
    mark[x] = !mark[x];
    int cur = x;
    if (mark[x]) {
        while (cur != -1) { ms[cur].insert(distc[x][cdep[cur]]); cur = cpar[cur]; }
    } else {
        while (cur != -1) {
            auto it = ms[cur].find(distc[x][cdep[cur]]);
            ms[cur].erase(it);
            cur = cpar[cur];
        }
    }
}
ll queryNode(int x) {
    ll ans = -1;
    int cur = x;
    while (cur != -1) {
        if (!ms[cur].empty()) {
            ll cand = distc[x][cdep[cur]] + *ms[cur].begin();
            if (ans == -1 || cand < ans) ans = cand;
        }
        cur = cpar[cur];
    }
    return ans;
}
int main() {
    fastio
    cin >> n >> q;
    for (int i = 1; i < n; i++) {
        int a, b; ll w;
        cin >> a >> b >> w;
        v[a].push_back({b, w});
        v[b].push_back({a, w});
    }
    decomposeAll();
    while (q--) {
        int t, x;
        cin >> t >> x;
        if (t == 1) toggle(x);
        else cout << queryNode(x) << endl;
    }
}

첫 번째 예제로 따라가 보기

트리는 간선 \(1\!-\!2\,(3)\), \(1\!-\!3\,(2)\), \(2\!-\!4\,(4)\), \(2\!-\!5\,(1)\)로 이루어집니다.
- 2 1 : 아직 보관소가 없으므로 \(-1\).
- 1 4 : 정점 4에 보관소 생성.
- 2 5 : 5에서 가장 가까운 보관소는 4이고 \(5 \to 2 \to 4 = 1 + 4 = 5\). 답 \(5\).
- 1 3 : 정점 3에 보관소 생성. (이제 보관소 = \(\{3, 4\}\).)
- 2 5 : 5에서 4까지는 \(5\), 5에서 3까지는 \(5 \to 2 \to 1 \to 3 = 1 + 3 + 2 = 6\). 최솟값 \(5\).
- 2 1 : 1에서 3까지 \(2\), 1에서 4까지 \(3 + 4 = 7\). 최솟값 \(2\).
- 1 4 : 정점 4의 보관소 제거. (보관소 = \(\{3\}\).)
- 2 5 : 5에서 3까지 \(6\). 답 \(6\).
- 1 3 : 정점 3 제거(이후 질의 없음).

출력은 -1 5 5 2 6으로 예제와 일치합니다. 센트로이드 분할은 이 과정을 정점 수에 무관하게 쿼리당 \(O(\log^2 N)\)으로 처리합니다.

평가 및 의견

알파카컵 1회: H - 알파카의 컵 보관소

개요
출제자 난이도 Diamond IV 다이아몬드 IV 의견 0 / 50 공개 집계 (커뮤니티 난이도, 주요 주제, 품질)는 의견이 충분히 모이면 공개됩니다.

Log in to rate problems.

개별 의견

아직 의견이 없습니다. 자격이 된다면 위 양식에서 가장 먼저 평가해 보세요.

풀이 제출

알파카컵 1회: H - 알파카의 컵 보관소

게스트로 둘러보고 있습니다. 로그인하면 풀이를 제출하고 진행 상황을 확인할 수 있습니다. 로그인하고 제출하기
공개
C++20 Tab 들여쓰기 · Ctrl+/ 주석 토글 · Enter 자동 들여쓰기
1 1 1 0 공백: 4 · UTF-8