리뷰
https://www.acmicpc.net/problem/15816
퀘스트 번호의 범위가 -10억~10억이므로 값/좌표 압축을 해야하는 문제인 것은 알았다, 다만 쿼리에서 신규 퀘스트 번호가 들어옴으로 추가로 인입된 퀘스트를 어떻게 압축해 줘야할까 생각하다가 오프라인 쿼리를 사용하여 AC를 받았다.
전역 변수
- n : 지금까지 달성한 퀘스트의 개수를 저장할 변수
- m : 쿼리의 개수를 저장할 변수
- Query : 쿼리 정보를 정의할 구조체
- VI : 값을 key로, 정렬된 인덱스를 value로 저장할 해시맵
- tree : 세그먼트 트리 정보를 저장할 배열
함수
1. update
void update(int node, int s, int e, int idx) {
if (s == e) tree[node]++;
else {
int mid = (s + e) / 2;
if (idx <= mid) update(node * 2, s, mid, idx);
else update(node * 2 + 1, mid + 1, e, idx);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
}
세그먼트 트리 업데이트를 위한 함수
2. query
int query(int node, int s, int e, int L, int R) {
if (R < s || e < L) return 0;
if (L <= s && e <= R) return tree[node];
int mid = (s + e) / 2;
return query(node * 2, s, mid, L, R) + query(node * 2 + 1, mid + 1, e, L, R);
}
세그먼트 트리의 구간합을 구하기 위한 함수
문제풀이
- n값을 입력 받고, 벡터 lst를 초기화 하여 n개의 이미 달성한 퀘스트의 번호를 lst에 추가해준다.
- m값을 입력 받고, Query타입의 벡터 queries를 초기화 하고, m개의 쿼리를 입력 받아준다.
- op가 1일경우 변수 val에 값을 추가로 입력 받고, lst에 val을 추가, 쿼리 정보를 queries에 추가해 준다.
- op가 2일경우 변수 L, R에 값을 추가로 입력 받고, lst에 L, R을 추가, 쿼리 정보를 queries에 추가해 준다.
- 벡터 sorted를 lst를 복사한 상태로 초기화 후 sort함수를 통해 오름차순으로 정렬해 준다.
- sorted를 erase와 unique함수를 통해 중복이 없는 상태로 만들어준다.
- 변수 all을 sorted의 size만큼의 값으로 저장 후 all만큼의 for문을 개행해 준다.
- VI의 key로 sorted[i]의 값을, value를 i로 하여 값/좌표 압축을 진행해 준다.
- n번의 for문을 개행하고, update함수를 통해 세그먼트 트리의 VI[lst[i]]번째 노드값을 증가시켜 준다.
- queries를 순회하며 op가 1이라면 update를, 2라면 query함수를 호출해 준다.
- query함수 수행 시 L, R에 해당하는 범위 내 구간합을 구하고 범위 - 구간합의 결과를 출력해 주면 된다.
트러블 슈팅
- 처음엔 set을 활용한 풀이를 구현하였다, 하지만 구간 내 클리어한 퀘스트를 구하는 방법이 선형 탐색 외에는 존재하지 않아 O(N)의 시간이 걸렸고, 시간 초과가 발생하게 되었다.
- 이후 세그먼트 트리를 활용한 방법을 구현하였다, 입/출력 최적화를 진행하지 않으니 마찬가지로 시간 초과가 발생하였다.
- ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);를 적용해 주니 AC를 받았다.
- 이후 tree를 벡터가 아닌 배열로 사용하고자 포팅을 해주었으나 범위를 8000000으로 했더니 OutofBounds에러가 발생하였다.
- op가 2일때 L, R값도 값/좌표 압축을 했으므로 최대 4000000개의 좌표 정보가 sorted벡터에 들어올 수 있었다.
- 이를 4배를 하여 tree배열의 범위를 1600만으로 설정하니 AC를 받았다.
참고 사항
- 추후에 들어올 좌표값도 알아야 하기에 오프라인 쿼리를 사용하는 방법으로 진행하였다.
- op가 2일경우 구간을 구하는 것을 이진탐색을 사용할까 고민했다. 하지만 정렬 후 해시맵에 저장하는 것과 별반 차이가 없을 것이라 생각하여 그냥 범위에 대한하는 좌표값도 같이 압축을 진행해 주었다.
- 다른 사람들의 풀이 결과를 보았을 때 내 풀이의 시공간 복잡도는 전혀 최적화 되지 않은 방법인 것 같다.
정답 코드
#include<iostream>
#include<vector>
#include<unordered_map>
#include<algorithm>
using namespace std;
int n, m;
struct Query {
int op, arg1, arg2;
};
unordered_map<int, int> VI;
int tree[16000000];
void update(int node, int s, int e, int idx) {
if (s == e) tree[node]++;
else {
int mid = (s + e) / 2;
if (idx <= mid) update(node * 2, s, mid, idx);
else update(node * 2 + 1, mid + 1, e, idx);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
}
int query(int node, int s, int e, int L, int R) {
if (R < s || e < L) return 0;
if (L <= s && e <= R) return tree[node];
int mid = (s + e) / 2;
return query(node * 2, s, mid, L, R) + query(node * 2 + 1, mid + 1, e, L, R);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
vector<int> lst;
for (int i = 0; i < n; ++i) {
int q; cin >> q;
lst.push_back(q);
}
cin >> m;
vector<Query> queries;
for (int i = 0; i < m; ++i) {
int op; cin >> op;
if (op == 1) {
int val; cin >> val;
lst.push_back(val);
queries.push_back({ op, val });
}
else {
int L, R; cin >> L >> R;
lst.push_back(L);
lst.push_back(R);
queries.push_back({ op, L, R });
}
}
vector<int> sorted = lst;
sort(sorted.begin(), sorted.end());
sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end());
int all = sorted.size();
for (int i = 0; i < all; ++i) {
VI[sorted[i]] = i;
}
for (int i = 0; i < n; ++i) {
update(1, 0, all - 1, VI[lst[i]]);
}
for (const Query& q : queries) {
int op = q.op;
if (op == 1) update(1, 0, all - 1, VI[q.arg1]);
else {
int cnt = query(1, 0, all - 1, VI[q.arg1], VI[q.arg2]);
cout << q.arg2 - q.arg1 + 1 - cnt << "\n";
}
}
}
728x90
'알고리즘 공부 > C++' 카테고리의 다른 글
[G5] 백준 17609번 회문 C++ 투 포인터, 문자열 (0) | 2025.05.07 |
---|---|
[G4] 백준 14497번 주난의 난(難) C++ 너비 우선 탐색, 플러드 필 (0) | 2025.05.06 |
[P5] 백준 14449번 Balanced Photo C++ 세그먼트 트리, 값/좌표 압축 (0) | 2025.05.04 |
[P5] 백준 23336번 A Sorting Problem C++ 세그먼트 트리 (0) | 2025.05.03 |
[P5] 백준 11861번 Maximal Area C++ 세그먼트 트리, 분할 정복 (0) | 2025.05.02 |