[백준] 2042번 - 구간 합 구하기 (C언어) [Gold1]

2023. 5. 31. 22:29·알고리즘

input output
5 2 2
1
2
3
4
5
1 3 6
2 2 5
1 5 2
2 3 5
17
12

해당 문제는 배열의 인덱스 요소에서 설정한 범위내에 구간의 합을 구하는 문제이다. 일반적인 배열의 구간 합은 O(n)의 시간 복잡도를 가지만 해당 문제에서 입력으로 주어지는 모든 수는 -2^63 보다 크거나 같고 2^63 - 1보다 작거나 같은 정수이므로 시간 제한이 2초이므로 보다 빠른 알고리즘을 사용해야 한다. 

 

필자는 세그먼트 트리를 통해 해결하였는데, 세그먼트 트리는 O(logn)의 상당히 빠른 알고리즘이다. 세그먼트 트리는 몰랐던 자료구조 이기 때문에 세그먼트 트리가 무엇인지 알아보면서 풀이를 진행하겠다. 

 

세그먼트 트리는 이진 트리 구조이다. 따라서 각 노드에 대한 정보를 알 수 있다.

  1. 부모노드 인덱스 * 2 = 왼쪽 자식 인덱스
  2. 부모노드 인덱스 * 2 + 1 = 오른쪽 자식 인덱스 

세그먼트 트리는 정보를 받아오는 배열로 부터 리프노드를 설정한다. 배열이 {1,2,3,4,5} 라면 세그먼트 트리 형태는 아래와 같다.

                        15

                6               9

          3         3      4       5

       1    2        

리프노드는 파란색으로 표시하였다. 해당 트리에서 알 수 있는 사실은 리프 노드 1과 2의 합은 3인데, 3은 1과 2의 부모노드이다. 이어서 3과 리프노드 3의 합은 6인데 이들의 부모노드는 6이다. 이에 대한 정보들을 담고 있는 것이 세그먼트 트리이다. 

 

세그먼트 트리를 생성하는 과정은 아래와 같다.

  1. 주어진 트리를 반으로 나눔
  2. 나누어진 트리에서 왼쪽 트리를 재귀 호출
  3. 나누어진 트리에서 오른쪽 트리를 재귀 호출
  4. 1~3 과정 반복

해당 과정에 대한 소스 코드는 아래와 같다.

ll MakeSegmentTree(int start, int end, int node) { // 트리 생성
	if (start == end) // 재귀 종료 조건
		return tree[node]=arr[start];

	int mid = (start + end) / 2; // 중간값 설정
	tree[node] =  MakeSegmentTree(start, mid, node * 2) + MakeSegmentTree(mid + 1, end, node * 2 + 1); 
	// 왼쪽 트리 반환 값 + 오른쪽 트리 반환 값 저장
	return tree[node]; // 세그먼트 트리 반환
}

트리를 생성하였으니 구간 합에 대하여 알아보자. 구간 합에서 생각해볼 수 있는 경우의 수는 아래와 같다.

  1. 탐색 범위가 완전히 벗어난 경우
  2. 탐색 범위가 완전히 속한 경우
  3. 탐색 범위가 위 두 조건을 제외하고 일부만 겹치는 경우

해당 연산에 대한 소스 코드는 아래와 같다.

ll sum(int start, int end, int node, int left, int right) { // 구간합 함수
	if (start > right || end < left) // 탐색 범위가 완전히 벗어난 경우
		return 0;
	if (left <= start && end <= right) // 탐색 범위가 완전히 속한 경우
		return tree[node];

	/* 탐색 범위가 위 두 경우를 제외하고 일부만 걸치는 경우 */
	int mid = (start + end) / 2;
	 
	return sum(start, mid, node * 2, left, right) + sum(mid + 1, end, node * 2 + 1, left, right);
}

이제 세그먼트 트리 값 변경에 대해서 알아보자. 위에서 알아본 구간 합의 조건과 유사하다.

  1. 변경하고자 하는 값이 탐색 범위에 완전히 속한 경우
  2. 변경하고자 하는 값이 탐색 범위에 완전히 벗어난 경우

해당 연산에 대한 소스 코드는 아래와 같다.

ll UpdateSegmentTree(int start, int end, int node, int index, ll diff) {
	if (index < start || index > end) // 범위를 벗어난 경우
		return;

	tree[node] += diff; // 트리 노드 업데이트
	
	if (start == end) // 재귀 종료 조건
		return;

	int mid = (start + end) / 2;
	UpdateSegmentTree(start, mid, node * 2, index, diff);
	UpdateSegmentTree(mid + 1, end, node * 2 + 1, index, diff);
}

 

전체 소스 코드는 아래와 같다. (주석 포함)

// 부모노드번호 x 2 = 왼쪽자식노드번호, 부모노드번호 x 2 + 1 = 오른쪽자식노드번호
// 세그먼트 트리 이용하여 문제 해결
//      10		
//   3       7
//  1 2    3  4

/*
	크기가 N인 배열이 존재할 때

	1. 트리의 높이 = ceil(log2(N))

	2. 세그먼트 트리의 크기 = (1 << (트리의 높이 + 1) )

*/

// { 현재 노드 번호 , 시작 범위 , 마지막 범위 }
// 인덱스 1부터 사용

/*
1. 주어진 범위를 반으로 나눈다.

2. 나눠진 2개의 범위에 대해서 '왼쪽범위'에 대한 재귀호출을 한다.

3. 나눠진 2개의 범위에 대해서 '오른쪽범위'에 대한 재귀호출을 한다.

4. 위의 과정을 반복한다.
*/

#include <stdio.h>
#pragma warning(disable:4996)
typedef long long int ll; // long long 타입 정의
ll arr[1000000];
ll tree[4000000];

ll MakeSegmentTree(int start, int end, int node) { // 트리 생성
	if (start == end) // 재귀 종료 조건
		return tree[node]=arr[start];

	int mid = (start + end) / 2; // 중간값 설정
	tree[node] =  MakeSegmentTree(start, mid, node * 2) + MakeSegmentTree(mid + 1, end, node * 2 + 1); 
	// 왼쪽 트리 반환 값 + 오른쪽 트리 반환 값 저장
	return tree[node]; // 세그먼트 트리 반환
}

ll sum(int start, int end, int node, int left, int right) { // 구간합 함수
	if (start > right || end < left) // 탐색 범위가 완전히 벗어난 경우
		return 0;
	if (left <= start && end <= right) // 탐색 범위가 완전히 속한 경우
		return tree[node];

	/* 탐색 범위가 위 두 경우를 제외하고 일부만 걸치는 경우 */
	int mid = (start + end) / 2;
	 
	return sum(start, mid, node * 2, left, right) + sum(mid + 1, end, node * 2 + 1, left, right);
}

ll UpdateSegmentTree(int start, int end, int node, int index, ll diff) {
	if (index < start || index > end) // 범위를 벗어난 경우
		return;

	tree[node] += diff; // 트리 노드 업데이트
	
	if (start == end) // 재귀 종료 조건
		return;

	int mid = (start + end) / 2;
	UpdateSegmentTree(start, mid, node * 2, index, diff);
	UpdateSegmentTree(mid + 1, end, node * 2 + 1, index, diff);
}

int main() {
	int n, x, y;
	scanf("%d %d %d", &n,&x,&y);

	for (int i = 0; i < n; i++) {
		scanf("%lld", &arr[i]);
	}

	MakeSegmentTree(0, n - 1, 1);
	int cnt = x + y;
	for (int i = 0; i < cnt; i++) {
		ll a, b, c;
		scanf("%lld %lld %lld", &a, &b, &c);
		if (a == 1) {
			UpdateSegmentTree(0, n - 1, 1, b - 1, c - arr[b - 1]);
			arr[b - 1] = c; // 배열 요소 업데이트 
		}
		else {
			printf("%lld\n", sum(0, n - 1, 1, b - 1, c - 1)); // 구간합 출력
		}
	}

	return 0;
}

해당 문제를 통해 새로운 자료구조를 접할 수 있어서 새로웠고 생각보다 문제가 까다로웠다. 

 

[2042번]

https://www.acmicpc.net/problem/2042

 

2042번: 구간 합 구하기

첫째 줄에 수의 개수 N(1 ≤ N ≤ 1,000,000)과 M(1 ≤ M ≤ 10,000), K(1 ≤ K ≤ 10,000) 가 주어진다. M은 수의 변경이 일어나는 횟수이고, K는 구간의 합을 구하는 횟수이다. 그리고 둘째 줄부터 N+1번째 줄

www.acmicpc.net

 

[github]

https://github.com/MinWook6457

 

MinWook6457 - Overview

MinWook6457 has 18 repositories available. Follow their code on GitHub.

github.com

[참고 사이트]

https://yabmoons.tistory.com/431

 

[ 세그먼트 트리(Segment Tree) ] 개념과 구현방법 (C++)

이번 글에서는 세그먼트 트리(Segment Tree) 에 대해서 이야기를 해보자. 1. 세그먼트 트리 ( Segment Tree ) ??가장 먼저, 세그먼트 트리가 무엇을 하기 위한 놈인지 부터 알아보자.세그먼트 트리는 "구간

yabmoons.tistory.com

 

'알고리즘' 카테고리의 다른 글

[백준] 11053번 - 가장 긴 증가하는 부분 수열 (C언어) [Silver2]  (0) 2023.06.02
[백준] 1517번 - 버블 소트 (C언어) [Platinum5]  (0) 2023.06.01
[백준] 1918번 - 후위 표기식 (C언어) [Gold2]  (0) 2023.05.30
[백준] 1937번 - 욕심쟁이 판다 (C언어) [Gold3]  (0) 2023.05.29
[백준] 1260번 - DFS와 BFS (C언어) [Silver2]  (0) 2023.05.28
'알고리즘' 카테고리의 다른 글
  • [백준] 11053번 - 가장 긴 증가하는 부분 수열 (C언어) [Silver2]
  • [백준] 1517번 - 버블 소트 (C언어) [Platinum5]
  • [백준] 1918번 - 후위 표기식 (C언어) [Gold2]
  • [백준] 1937번 - 욕심쟁이 판다 (C언어) [Gold3]
min._.uuk_
min._.uuk_
하루 하나 기록하기
  • min._.uuk_
    기록하는 습관
    min._.uuk_
  • 전체
    오늘
    어제
    • 분류 전체보기 (33)
      • 알고리즘 (15)
      • 자바 스크립트 (2)
      • 인실리코젠 (15)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    백준 #후위표기식 #스택 #중위표기식 #우선순위 #1918번
    백준 #세그먼트 트리 #구간합 #c언어 #골드1 #탐색
    백준 #1407번 #재귀함수
    JS #테트리스 #HTML #CSS #추억의 게임
    백준 #MST #최소비용신장트리 #크루스칼 #그리디 #알고리즘
    Apache #POI #JAVA #maven
    백준 #동적계획법 #dp #백트래킹 #dfs
    #BOJ #백준 #10989번 #C언어 #카운팅정렬 #정렬
    인실리코젠 #크롤링 #SpringBoot #MVC #jQuery
    백준 #BOJ #스택 #수열
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
min._.uuk_
[백준] 2042번 - 구간 합 구하기 (C언어) [Gold1]
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.