ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 백준 2042(구간 합 구하기)
    전공/알고리즘 2020. 6. 6. 17:40

    이 문제는 전형적인 세그먼트 트리 문제이다. 

     

    세그먼트 트리는 어떤 연속된 구간의 합 차 등 어떤 계산의 반복을 미리 쪼개어 놔서 시간을 월등히 줄여주는 자료구조이다.

    특징은 완전 이진 트리로 구성되어 배열 자료구조를 사용하며 맨 위 노드의 index를 1로 잡아서 좌측 노드는 2*index, 우측 노드는 2*index+1이다.

     

    위 특징을 이용해서 구현을 진행한다.

     

    단순 계산으로는 O(쿼리의 횟수 * 배열의 크기) O(mn)의 시간복잡도가

     

    세그먼트 트리로는 O(mlogn)로 줄어든다.

     

     

    코드의 구성은

     

    세그먼트 트리 구현,

    쿼리 함수 구현

     

    이렇게 나뉜다고 볼 수 있다.

     

    세그먼트 트리의 구현은 n의 값이 2의 거듭제곱꼴이 아닌 어떤 자연수이므로,

    top - down 형식의 재귀적 방법을 사용한다. 

     

    앞선 문제에서 언급했듯이 재귀적 구성은 

    leaf 노드까지 도달한 뒤 다시 재귀적 특성을 이용해서 올라와서 나머지 노드들을 채우는 꼴로 진행된다. 

     

    ll init(int node, int left, int right) {
    	if (left == right) return tree[node] = arr[left]; // leaf 노드의 값 
    
    	int mid = (left + right) / 2;
    	ll m1 = init(node * 2, left, mid); // 우측 자식
    	ll m2 = init(node * 2 + 1, mid + 1, right); // 좌측 자식
    	return tree[node] = m1+m2; // 우측과 좌측 자식의 합 (문제 조건)
    }
    

     쿼리 함수는 두가지로 int query와 void change 로 두 함수를 구성하였다.

     

    int query 함수

    ll query(int node, int start, int end, int left, int right) {
    	if (left <= start && right >= end) return tree[node]; // 찾고자 하는 구간과 노드의 구간 비교
    	if (left > end || right < start) return 0; // 구간을 벗어난 경우
    	int mid = (start + end) / 2;
    	ll m1 = query(node * 2, start, mid, left, right); // 좌측 자식
    	ll m2 = query(node * 2 + 1, mid + 1, end, left, right); // 우측 자식
    	return m1 + m2; 
    } 
    

    void change 함수

     

    void change(int node, int start, int end, int index, int after) {
    	if (start == end && start == index) { //찾는 index인 경우
    		tmp = after-tree[node]; //차이를 저장 
    		tree[node] = after;
    		return;
    	}
    	int mid = (start + end) / 2; 
    	if (index > mid) { 
    		change(node * 2 + 1, mid + 1, end, index, after);
    	}
    	else {
    		change(node * 2, start, mid, index, after);
    	}
    	tree[node] += tmp; //해당 index값이 쓰인 모든 부모노드의 값 + 차이 
    }
    
    #include<iostream>
    #include<cmath>
    #include<vector>
    using namespace std;
    
    typedef long long ll;
    
    vector<ll> arr;
    vector<ll> tree;
    int N, M, K, a, b, c;
    ll tmp;
    
    ll init(int node, int left, int right) {
    	if (left == right) return tree[node] = arr[left];
    
    	int mid = (left + right) / 2;
    	ll m1 = init(node * 2, left, mid);
    	ll m2 = init(node * 2 + 1, mid + 1, right);
    	return tree[node] = m1+m2;
    }
    
    ll query(int node, int start, int end, int left, int right) {
    	if (left <= start && right >= end) return tree[node];
    	if (left > end || right < start) return 0;
    	int mid = (start + end) / 2;
    	ll m1 = query(node * 2, start, mid, left, right);
    	ll m2 = query(node * 2 + 1, mid + 1, end, left, right);
    	return m1 + m2;
    }
    
    void change(int node, int start, int end, int index, int after) {
    	if (start == end && start == index) {
    		tmp = after-tree[node];
    		tree[node] = after;
    		return;
    	}
    	int mid = (start + end) / 2;
    	if (index > mid) {
    		change(node * 2 + 1, mid + 1, end, index, after);
    	}
    	else {
    		change(node * 2, start, mid, index, after);
    	}
    	tree[node] += tmp;
    }
    
    int main() {
    	ios_base::sync_with_stdio(false);
    	cin.tie(0);
    	cout.tie(0);
    
    	cin >> N >> M >> K;
    	arr.resize(N);
    	int h = (int)ceil(log2(N));
    	int tree_size = (1 << (h + 1));
    	tree.resize(tree_size);
    	for (int i = 0; i < N; i++)
    		cin >> arr[i];
    	init(1, 0, N - 1);
    	for (int i = 0; i < M + K; i++) {
    		tmp = 0;
    		cin >> a >> b >> c;
    		if (a == 1) {
    			change(1, 0, N - 1, b-1, c);
    		}
    		else if (a == 2) {
    			cout << query(1, 0, N - 1, b-1, c-1) << "\n";
    		}
    	}
    }

    전형적인 세그먼트 구현 문제이고 개념도 쉽게 터득할 수 있는 것 같다. 재귀적 성질에 대한 이해만 제대로 된다면.

    '전공 > 알고리즘' 카테고리의 다른 글

    백준 1080(행렬)  (0) 2020.06.08
    백준 1541(잃어버린 괄호)  (0) 2020.06.08
    백준 1725(히스토그램)세그먼트 트리  (0) 2020.06.06
    백준 12846(무서운 아르바이트)스택  (0) 2020.06.05
    백준 3079(입국심사)  (0) 2020.06.03

    댓글

Designed by Tistory.