BOJ::2042 구간 합 구하기

시사점

// 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)

디버깅(x)

좋은 코드

최적화