리뷰
https://www.acmicpc.net/problem/15899
서브 트리에서 특정 값 이하의 개수를 구하는 문제
전역 변수
- N : 정점의 개수를 저장할 변수
- MOD : 모듈러 연산을 위한 변수
- lst : 각 노드의 색을 저장할 배열
- color : 노드 방문 정보를 기준으로 색을 저장할 배열
- edges : 인접 리스트를 저장할 벡터 배열
- n : 정점의 개수
- m : 쿼리의 개수를 저장할 변수
- c : 색의 최대값을 저장할 변수
- it : 트리 탐색 시 진입 시간을 저장할 배열
- ot : 트리 탐색 시 탈출 시간을 저장할 배열
- t : 트리 탐색 시 시간을 저장할 변수
- ans : 정답을 저장할 변수
- tree : 세그먼트 트리 정보를 저장할 배열
함수
1. dfs
void dfs(int node, int par)
깊이 우선 탐색을 통해 오일러 경로를 최신화 하기 위한 함수
- 매개 변수로 현대 노드 mode와 부모 노드 par을 전달 받는다.
- it[node]에 t를 전위 증가한 값을 저장해 준다.
- color[it[node]]의 값을 현재 노드의 색 lst[node]로 저장해 준다.
- node의 인접 리스트를 순회하며 재귀를 진행해 준다.
- 모든 인접 리스트 잡색을 마친 뒤 ot[node]에 t값을 저장해 준다.
2. build
void build(int node, int s, int e)
세그먼트 트리 정보를 초기화 하기 위한 함수
- 매개 변수로 노드 정보 node, 탐색 범위 s, e를 전달 받는다.
- 리프 노드에 도달한 경우 해당 노드에 color[s]혹은 color[e]값을 벡터로 저장해 준다.
- 아닌 경우 좌, 우 자식 노드로 재귀를 진행해 준다.
- 재귀를 빠져나온 후 왼쪽 및 오른쪽 자식 노드를 벡터 변수 left, right로 가져온다.
- 벡터 par을 left, right 크기의 합으로 초기화 해준다.
- merge함수를 통해 left, right를 병합 정렬하여 par의 begin에 요소를 삽입해 준다.
- 현재 노드에 par값을 저장해 준다.
3. query
int query(int node, int s, int e, int L, int R, int C)
서브 트리에서 C이하인 요소 개수를 구하는 함수
- 매개 변수로 노드 정보 node, 탐색 범위 s, e, 구간 범위 L, R, 값 C를 전달 받는다.
- 첫 번째 기저 조건으로 범위를 벗어난 경우 0을 리턴해 준다.
- 두 번째 기저 조건으로 탐색 범위가 구간에 포함될 경우 upper_bound함수를 통해 현재 노드으 벡터에서 C이하인 요소의 개수를 구해 개수를 리턴해 준다.
- 좌, 우 자식 노드로 재귀 탐색을 진행하고 각각 받은 리턴 값을 변수 left, right에 저장해 준다.
- left와 right를 더한 값을 리턴해 준다.
문제풀이
- n, m, c값을 입력 받는다.
- n개의 노드의 색 정보를 lst배열에 입력 받아준다.
- n - 1개의 간선 정보를 입력 받아 인접 리스트에 양방향으로 추가해 준다.
- dfs함수에 1, 1을 매개 변수로 전달해 오일러 경로를 최신화 해준다.
- build 함수를 통해 오일러 경로를 기반으로 세그먼트 트리를 초기화 해준다.
- m개의 쿼리를 입력 받아 매 쿼리 마다 노드 번호 V, 값 C를 입력 받아준다.
- query 함수를 통해 it[V] ~ ot[V] 범위에서 C이하인 요소의 개수를 구해 ans에 더해준다.
- 만약 ans가 MOD보다 커졌을 경우 나머지 연산을 진행해 준다.
- 모든 탐색을 진행한 후에 ans에 저장된 값을 출력해 준다.
트러블 슈팅
- 어차피 트리이므로 n - 1개의 간선을 단 방향으로 추가했으나 Fail을 받았다.
- 루트 번호가 1이라는 보장이 없기 때문에 양방향으로 추가하고 dfs내부에 조건을 추가해 주었다.
- query 함수의 반환값을 벡터로 하고, 매번 merge를 수행해 줬더니 시간 초과를 받았다.
- 반환값을 int로 변경하고 내부에서 리턴할 때 upper_bound를 수행해 주었더니 AC를 받았다.
참고 사항
- 예제는 엣지케이스 탐색에 별 도움이 되지 않는다.
정답 코드
#include<iostream>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
const int N = 200001;
const int MOD = 1000000007;
int lst[N];
int color[N];
vector<int> edges[N];
int n, m, c;
int it[N];
int ot[N];
int t;
ll ans;
vector<int> tree[N * 4];
void dfs(int node, int par) {
it[node] = ++t;
color[it[node]] = lst[node];
for (int i : edges[node]) {
if (par == i) continue;
dfs(i, node);
}
ot[node] = t;
}
void build(int node, int s, int e) {
if (s == e) tree[node] = { color[s] };
else {
int mid = (s + e) / 2;
build(node * 2, s, mid);
build(node * 2 + 1, mid + 1, e);
vector<int>& left = tree[node * 2];
vector<int>& right = tree[node * 2 + 1];
vector<int> par(left.size() + right.size());
merge(left.begin(), left.end(), right.begin(), right.end(), par.begin());
tree[node] = par;
}
}
int query(int node, int s, int e, int L, int R, int C) {
if (R < s || e < L) return 0;
if (L <= s && e <= R) return upper_bound(tree[node].begin(), tree[node].end(), C) - tree[node].begin();
int mid = (s + e) / 2;
int left = query(node * 2, s, mid, L, R, C);
int right = query(node * 2 + 1, mid + 1, e, L, R, C);
return left + right;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m >> c;
for (int i = 1; i <= n; ++i) cin >> lst[i];
for (int i = 0; i < n - 1; ++i) {
int a, b; cin >> a >> b;
edges[a].push_back(b);
edges[b].push_back(a);
}
dfs(1, 1);
build(1, 1, n);
while (m--) {
int V, C; cin >> V >> C;
ans += query(1, 1, n, it[V], ot[V], C);
if (ans >= MOD) ans %= MOD;
}
cout << ans;
}
728x90
'알고리즘 공부 > C++' 카테고리의 다른 글
[S1] 백준 29197번 아침 태권도 C++ 수학, 기하학, 해시맵 (0) | 2025.02.26 |
---|---|
[G5] 백준 19940번 피자 오븐 C++ 너비 우선 탐색 (0) | 2025.02.26 |
[G5] 백준 15662번 톱니바퀴 (2) C++ 덱, 구현, 시뮬레이션 (0) | 2025.02.26 |
[S3] 백준 2149번 암호 해독 C++ 문자열, 정렬 (0) | 2025.02.26 |
[G4] 백준 14267번 회사 문화 1 C++ 세그먼트 트리, 느리게 갱신되는 세그먼트 트리, 오일러 경로 테크닉 (0) | 2025.02.26 |