BOJ
BOJ::2042 구간 합 구하기
BOJ Platinum 5
BOJ::2042 구간 합 구하기
- Link : BOJ::2042
- Link : beenpow::1275::커피숍2
- Link : beenpow::FenWickTree
- Level : Platinum 5
시사점
- 세그먼트 트리 기본 문제입니다.
- BOJ의 커피숍2와 거의 비슷한 문제입니다.
- 입력받는 부분만 제외하면 코드가 동일합니다.
- 따라서, 기본적으로 세그먼트 트리로 구현한 코드와 펜윅트리로 구현한 코드를 업로드하였습니다.
- 첫 Fenwick 트리 문제이고, 기본 문제인 만큼 정형화 시켜두면 좋을 것 같습니다.
- 총 3개의 함수가 필요합니다.
- precalc(), fenwickSum(), fenwickAdd()
- 첨언하자면, precalc()함수의 구현내용과 같이 2가지 방법으로 초기화가 가능합니다.
- fenwickSum에서는 pos &= (pos-1) 혹은 pos -= (pos & -pos)를 이용하여 다음 구간을 찾습니다.
- fenwickAdd에서는 pos += (pos & -pos)를 이용하여 다음 구간을 찾습니다.
- pos +- (pos & -pos)로 생각하면 두 방법 모두에 대한 pos 변경을 쉽게 떠올릴 수 있을 것 같습니다.
// fenwick tree의 구간합을 반환합니다.
// 구간 [0, pos]의 합을 반환합니다.
ll FenwickSum(int pos){
ll ret = 0;
while(pos > 0){
ret += tree[pos];
// pos 변경 방법 1
//pos &= (pos-1);
// pos 변경 방법 2
pos -= (pos & -pos);
}
return ret;
}
// fenwick tree를 업데이트 합니다.
// pos에 해당하는 position을 포함하는 tree 인자에 val값을 더해줍니다.
void FenwickAdd(int pos, ll val){
while(pos <= tree.size()){
tree[pos] += val;
pos += (pos & -pos);
}
}
키
이해(x)
설계, 손 코딩(x)
- 손으로 중심이 되는 코드를 구현합니다.
시간 복잡도
공간 복잡도
손 코딩 후 문제 리뷰(x)
- 빼먹고 손 코딩 한 것이 있는지 확인합니다.
구현(x)
함수 List
// fenwick tree의 구간합을 반환합니다.
// 구간 [0, pos]의 합을 반환합니다.
ll FenwickSum(int pos);
// fenwick tree를 업데이트 합니다.
// pos에 해당하는 position을 포함하는 tree 인자에 val값을 더해줍니다.
void FenwickAdd(int pos, ll val);
// tree[i]를 초기화 합니다.
// 이때 tree[i]는 오른쪽 끝 위치가 a[i]인 구간의 합을 의미합니다.
void precalc();
업데이트 되는 변수
- 대부분의 디버깅 문제는 업데이트 되는 변수에서 발생합니다.
vector<ll>tree;
// 이때 tree[i]는 오른쪽 끝 위치가 a[i]인 구간의 합을 의미합니다.
실제 구현 ( 펜윅 트리 )
#include<bits/stdc++.h>
typedef long long ll;
const int MAXNQ = 1000 * 1000 + 1;
using namespace std;
int n, m, k;
int a[MAXNQ];
vector<ll> tree;
ll FenwickSum(int pos){
ll ret = 0;
while(pos > 0){
ret += tree[pos];
// pos 변경 방법 1
//pos &= (pos-1);
// pos 변경 방법 2
pos -= (pos & -pos);
}
return ret;
}
void FenwickAdd(int pos, ll val){
while(pos <= tree.size()){
tree[pos] += val;
pos += (pos & -pos);
}
}
void precalc(){
tree = vector<ll> (n + 1);
// 초기화 방법 1
// for(int i = 1; i <= n; i++)
// FenwickAdd(i, a[i]);
// 초기화 방법 2
for(int i =1; i<=n; ++i){
int J = i & -i;
for(int j =0; j<J; ++j) tree[i] += a[i-j];
}
}
int main(){
freopen("input.txt", "r", stdin);
ios_base::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> k;
for(int i = 1; i <= n; i++) cin >> a[i];
precalc();
m += k;
while(m--){
int ind, x, y;
cin >> ind >> x >> y;
if(ind == 2){
cout << FenwickSum(y) - FenwickSum(x-1) << '\n';
}else{
ll diff = y - a[x];
a[x] = y;
FenwickAdd(x, diff);
}
}
return 0;
}
실제 구현 ( 세그먼트 트리 )
#include<bits/stdc++.h>
typedef long long ll;
const int MAXNQ = 1000 * 1000;
const ll _LL_MAX = std::numeric_limits<long long>::max();
using namespace std;
int n, m, k;
// 업데이트 되는 변수 -----------------------------
// left, right는 a[]에서의 인덱스를 의미합니다.####################################
ll a[MAXNQ]; // a[i] = i번째 인덱스에 대한 값을 의미합니다.
vector<ll> rangeSum; // rangeSum[node] = rangeSum[node*2] + rangeSum[node*2+1]
// 즉 좌측 서브트리와 우측 서브트리의 합을 넣습니다.
// 리프노드인 경우 rangeSum[node] = a[left] 입니다.
// 업데이트 되는 변수 -----------------------------
// 노드 rangeSum[node]에 a[]의 구간 [left, right]에 대한 합을 반환합니다.
ll init(vector<ll>& arr, int left, int right, int node){
if(left == right) return rangeSum[node] = a[left];
int mid = (left + right) / 2;
ll leftSum = init(arr, left, mid, node*2);
ll rightSum = init(arr, mid+1, right, node*2 +1);
return arr[node] = leftSum + rightSum;
}
// 세그먼트 트리를 위한 초기화를 진행합니다.
void precalc(){
// 구간의 길이는 n * 4 로 할 경우 충분합니다.
rangeSum.resize(n * 4);
init(rangeSum, 0, n-1, 1);
}
// 구간 [left, right] 에 대해서 구간 합을 반환합니다.
// 이때 트리는 nodeLeft부터 nodeRIght까지의 구간을 표현합니다.
ll query(int left, int right, int node, int nodeLeft, int nodeRight){
if(right < nodeLeft || nodeRight < left) return _LL_MAX;
// [left, nodeLeft, nodeRight, right] 순서인 경우 ( 즉, 완전 포함 )
if(left <= nodeLeft && nodeRight <= right)
return rangeSum[node];
int mid = (nodeRight + nodeLeft) / 2;
ll leftSum = query(left, right, node * 2, nodeLeft, mid);
ll rightSum = query(left, right, node * 2 + 1, mid+1, nodeRight);
return (leftSum == _LL_MAX ? 0 : leftSum) + (rightSum == _LL_MAX ? 0 : rightSum);
}
// a[idx] = newValue로 갱신되었을때, node를 루트로 하는 구간트리를 갱신하고
// 노드가 갖는 범위의 합을 반환한다.
ll update(int idx, int newValue, int node, int nodeLeft, int nodeRight){
// index와 현재 노드가 상관없는 경우 무시한다.
if( idx < nodeLeft || nodeRight < idx) return rangeSum[node];
// 트리의 리프까지 내려온 경우
if(nodeLeft == nodeRight) return rangeSum[node] = newValue;
int mid = (nodeLeft + nodeRight) / 2;
ll leftSum = update(idx, newValue, node * 2, nodeLeft, mid);
ll rightSum = update(idx, newValue, node * 2 + 1, mid+1, nodeRight);
return rangeSum[node] = leftSum + rightSum;
}
int main(){
freopen("input.txt", "r", stdin);
ios_base::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> k;
for(int i = 0; i < n; i++) cin >> a[i];
precalc();
m += k;
while(m--){
int ind, x, y;
cin >> ind >> x >> y;
if(ind == 2){
cout << query(x-1, y-1, 1, 0, n-1) << '\n';
}else{
update(x-1, y, 1, 0, n-1);
}
}
return 0;
}
구현 후 코드리뷰 + 예제 돌려보기(x)
- 바로 예제를 넣어서 테스트 해보기 전에, 코드를 한 번 리뷰하고, 예제의 입력이 이런식으로 왔을때 전체 코드가 어떤 식으로 순차적으로 진행되는지 답은 잘 도출될 것 같은지 확인합니다.