누적 합 알고리즘 | 코딩 테스트 \U0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum) 185 개의 정답

당신은 주제를 찾고 있습니까 “누적 합 알고리즘 – 코딩 테스트 \u0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum)“? 다음 카테고리의 웹사이트 ppa.cazzette.com 에서 귀하의 모든 질문에 답변해 드립니다: https://ppa.cazzette.com/blog/. 바로 아래에서 답을 찾을 수 있습니다. 작성자 동빈나 이(가) 작성한 기사에는 조회수 29,353회 및 좋아요 619개 개의 좋아요가 있습니다.

Table of Contents

누적 합 알고리즘 주제에 대한 동영상 보기

여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!

d여기에서 코딩 테스트 \u0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum) – 누적 합 알고리즘 주제에 대한 세부정보를 참조하세요

코딩 테스트 \u0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum)

누적 합 알고리즘 주제에 대한 자세한 내용은 여기를 참조하세요.

누적합(prefix sum) – 코딩무식자 전공생 – 티스토리

누적합은 말 그대로 구간의 누적합을 구하는 문제입니다. 일반적으로 사용되는 배열에 값을 저장하고 지정된 인덱스부터 하나씩 더해가는 방식은 최악 …

+ 여기에 더 보기

Source: jow1025.tistory.com

Date Published: 8/16/2022

View: 3183

누적 합

누적 합 (Prefix Sum) … 배열이 변하지 않으니 구간의 합도 변하지 않습니다. 따라서, 앞에서부터 차례대로 누적된 합을 구해놓고 이를 이용해서 구간의 합을 구할 …

+ 자세한 내용은 여기를 클릭하십시오

Source: book.acmicpc.net

Date Published: 10/7/2022

View: 5270

[알고리즘] 부분합, 누적합 (Prefix Sum) 쉽게 알아보기(파이썬)

부분합, 누적합이라고 불리우는 배열의 일부 구간에 대한 합을 매우 빠르게 구할 수 있게 해주는 스킬이다. N개의 원소로 이루어진 배열이 주어졌을 …

+ 여기에 표시

Source: yiyj1030.tistory.com

Date Published: 5/2/2021

View: 5767

[Java]누적 합(Prefix Sum , Cumulative Sum) – TH – 티스토리

Algorithm/수학&기타 … 누적 합이란, 말 그대로 나열된 수의 누적된 합을 말한다. … 자연수를 나타내는 수열 An의 5항까지의 누적 합.

+ 여기를 클릭

Source: sskl660.tistory.com

Date Published: 9/10/2022

View: 2341

누적합(Prefix Sum / Cumulative Sum) 알고리즘 – 코딩 헬스장

누적합(Prefix Sum / Cumulative Sum) 알고리즘 · 누적 합 이란 수열 An에 대해서 각 인덱스까지의 구간의 합을 구하는 것을 누적 합이라고 합니다.

+ 여기에 자세히 보기

Source: ji-musclecode.tistory.com

Date Published: 7/12/2021

View: 1100

누적 합(prefix sum), 2차원 누적합(prefix sum of matrix) with java

다만 구현에 있어 누적 합을 구현할 때는 인덱스 1번부터 사용하는게 훨씬 … 하지만 우린 이제 누적합 알고리즘을 쓸줄아니깐 각 행별로 누적합을 …

+ 더 읽기

Source: nahwasa.com

Date Published: 10/26/2022

View: 5327

누적합 계산하기 – 만년 꼴지 공대생 세상 이야기

누적합은 특정 수열이 주어지고, 그 수열에서 특정 구간의 값을 구하라는 쿼리가 반복적으로 들어올 때 주로 사용하게 되는 개념이다. 예를 들어 길이 n의 …

+ 여기에 표시

Source: ojt90902.tistory.com

Date Published: 1/19/2021

View: 4878

[ 개념 ] 52. Prefix Sum(누적 합, 구간 합)

구간 합은 a ~ b까지의 합을 의미 합니다. 이러한 알고리즘은 어디에 쓰일까요? 아래와 같은 문제를 예시로 봅시다. int arr[10] = {0, 1, …

+ 더 읽기

Source: coder-in-war.tistory.com

Date Published: 6/22/2021

View: 2690

[Algorithm] 2차원 배열 부분합, 누적합 구하기 – velog

출처. 2차원 누적합, 부분합 구하기2차원배열에서 부분 배열의 합을 구하는 방법을 알아보자. 이중 for를 이용해서 구할 수 있지만 시간복잡도가 …

+ 여기에 표시

Source: velog.io

Date Published: 7/29/2022

View: 2656

[Algorithm] 누적 합(Prefix sum) 알고리즘 개념 및 코드

누적 합 Prefix sum 이번 포스팅에선 누적 합 알고리즘을 살펴보겠다. 꽤 간단하지만 알고리즘이지만, 의외로 모르는 사람이 꽤 있다.

+ 여기를 클릭

Source: mengu.tistory.com

Date Published: 6/29/2021

View: 7261

주제와 관련된 이미지 누적 합 알고리즘

주제와 관련된 더 많은 사진을 참조하십시오 코딩 테스트 \u0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum). 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.

코딩 테스트 \u0026 알고리즘 대회 핵심 노트 - 투 포인터(Two Pointers), 구간 합(Prefix Sum)
코딩 테스트 \u0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum)

주제에 대한 기사 평가 누적 합 알고리즘

  • Author: 동빈나
  • Views: 조회수 29,353회
  • Likes: 좋아요 619개
  • Date Published: 2020. 5. 9.
  • Video Url link: https://www.youtube.com/watch?v=rI8NRQsAS_s

누적합(prefix sum)

누적합은 말 그대로 구간의 누적합을 구하는 문제입니다.

일반적으로 사용되는 배열에 값을 저장하고 지정된 인덱스부터 하나씩 더해가는 방식은 최악의 경우O(n^2)의 시간복잡도를 갖기 때문에 입력의범위가 클 때 사용할 수 없습니다. 하지만 Prefix sum방식을 사용하면 O(N)으로 해결 할 수 있습니다.

누적합은 문제에서 수열이 주어지고 어떤 구간의 값의 합을 구해야 할 때 쓰일 수 있습니다.

예를들어 크기가 5인arr배열에서 3번index와 5번index구간의 구간합을 구한다고 가정하면, 누적합은 arr[0~b까지의 누적합] – arr[0~a-1까지의누적합]으로 표현할 수 있습니다.

그림을 보면 쉽게 이해할 수 있습니다. b-a구간의 누적합을 구하기위해선 b구간까지의 합- a-1구간까지의 합을 빼주면됩니다.

[3,5]구간의 누적합은??

1. 크기가 5인 배열선언( 인덱스1~5)

2. 각 인덱스값에 누적합 저장

3. 3번구간과 5번 구간 사이의 누적합을 구하려면 겹치는 1,2구간을 제외해줘야됨.

즉, 2번인덱스의 누적합을 빼줘야함.

위의 표에서 분홍색부분이 겹치는 부분,즉 빼줘야하는 부분 이고 검은색부분 이 답을 구할 범위다.

코드

#include using namespace std; //인덱스1부터 가정하기위해 크기6으로생성 int arr[6] = { 0,7,6,3,2,1 }; int main() { //arr에 누적합을 저장한다. for (int i = 1; i <=5; i++) { arr[i] = arr[i - 1] + arr[i]; } //[3,5]구간 누적합 구하기 cout << arr[5] - arr[3 - 1] << ' '; } 문제풀고 복습하기 https://www.acmicpc.net/problem/11441 https://www.acmicpc.net/problem/11659

누적 합

누적 합 아이디어는 배열 $A$에 들어있는 값이 바뀌지 않는다는 점을 이용합니다. 배열이 변하지 않으니 구간의 합도 변하지 않습니다. 따라서, 앞에서부터 차례대로 누적된 합을 구해놓고 이를 이용해서 구간의 합을 구할 수 있습니다.

$S[i] = A[1] + \dots + A[i]$, $S[0] = 0$ 으로 정의합시다.

$l$번째 수부터 $r$번째 수까지 합은 $S[r] – S[l-1]$과 같습니다.

그 이유를 알아보면

$S[r] = A[1] + … + A[r]$

$S[l-1] = A[1] + … + A[l-1]$

입니다. 따라서, $S[r] – S[l-1] = A[l] + \dots + A[r]$이 됩니다.

구간의 합을 구하기 위해서 뺄셈 연산 한 번만 하면되니 시간 복잡도는 $O(1)$입니다. 연산을 총 $M$번 수행해야 하니 총 시간 복잡도는 $O(M)$이 됩니다.

[알고리즘] 부분합, 누적합 (Prefix Sum) 쉽게 알아보기(파이썬)

반응형

부분합, 누적합이라고 불리우는 배열의 일부 구간에 대한 합을 매우 빠르게 구할 수 있게 해주는 스킬이다.

N개의 원소로 이루어진 배열이 주어졌을 때 반복문을 통해 부분 배열의 합을 구하려면 O(N)이 걸리는데, 부분합을 이용하면 모든 부분합을 O(1)에 바로바로 구할 수 있다.

1차원 배열

– 직관적으로 매우 쉽게 이해가 가능하다. arr을 순차 탐색하면서 sum 배열을 만들어주면 된다.

sum[i]에는 arr[0] + arr[1] + … + arr[i-1]의 정보가 담겨 있다고 생각하면 된다.

– 활용법

arr의 i항부터 j항까지의 합을 S(i, j)라고 하자.

이때 S(i, j) = sum[j+1] – sum[i]이다.

2차원 배열

– 2차원 배열도 같은 방식이다. arr을 순차 탐색하면서 sum 배열을 만들어주면 된다.

이때 sum[i][j]에는 arr[0][0]부터 arr[i-1][j-1]까지의 합이 담겨 있다고 생각하면 된다.

그림을 보면 바로 이해 가능

이때 그럼 sum 배열은 어떻게 만들지?? 아래와 같이 반복문 돌려주면 된다.

이때 기억해놓으면 좋은 식은 sum_arr[i][j] = arr[i-1][j-1] + sum_arr[i-1][j] + sum_arr[i][j-1] – sum_arr[i-1][j-1]

arr = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]] # 가로길이 4, 세로 길이 3 m = 4 n = 3 sum_arr = [[0 for _ in range(m+1)] for _ in range(n+1)] for i in range(1, n+1): for j in range(1, m+1): sum_arr[i][j] = arr[i-1][j-1] + (sum_arr[i-1][j]) + (sum_arr[i][j-1]) – (sum_arr[i-1][j-1]) # 결과 보기 print(‘sum_arr : ‘) for i in range(n+1): print(sum_arr[i])

그럼 예상한 결과가 잘 나온다.

sum_arr : [0, 0, 0, 0, 0] [0, 1, 3, 6, 10] [0, 3, 8, 15, 24] [0, 6, 15, 27, 42]

– 2차원 구간합 활용법(중요!!)

이제 이 sum_arr을 활용하면 2차원 배열에서도 상수 시간에 구간합을 구할 수 있게 된다.

arr의 (x1, y1)부터 (x2, y2)까지 합을 S라 할 때,

S = sum[x2+1][y2+1] – sum[x2][y2+1] – sum[x2+1][y2] + sum[x1][y1]로 구할 수 있다.

-진짜 진짜 활용(중요!)

이 문제를 풀어보자!

https://programmers.co.kr/learn/courses/30/lessons/92344

반응형

[Java]누적 합(Prefix Sum , Cumulative Sum)

*누적 합(Prefix Sum, Cumulative Sum)

-> 누적 합이란, 말 그대로 나열된 수의 누적된 합을 말한다.

-> 조금 더 엄밀히 말하면, 수열 An에 대해서, 구간 [1, 1]의 합, 구간 [1, 2]의 합, 구간 [1, 3]의 합, …, [1, n]의 합을 누적 합이라고 한다.

자연수를 나타내는 수열 An의 5항까지의 누적 합

※ 따라서 Prefix Sum의 각 요소는 해당 해당 인덱스까지의 부분 합(Partial Sum)을 의미한다. 부분 합은 급수에서 말하는 그 부분합의 의미가 맞다.

*누적 합의 사용

-> 누적 합은 그 목적에 따라 다양한 문제에 활용이 가능하다.

-> 대표적으로 누적 합을 사용하는 문제는 카운팅 정렬(Counting Sort), 구간 합 구하기가 존재한다.

*단순 반복을 이용한 구간 합 구하기

-> 이 글의 핵심 목적이다. 일반적으로 구간의 합을 구하는 경우에는 다음과 같이 모든 구간의 값을 더해주는 방법을 생각해볼 수 있다.

import java.util.Scanner; public class PrefixSum { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int[] arr2 = {1, 5, 8, 10, 24, 3, 5, 100, 99, 7}; int a = sc.nextInt(); int b = sc.nextInt(); int sum1 = 0; int sum2 = 0; for(int i = a; i <= b; i++) { sum1 += arr[i]; sum2 += arr2[i]; } System.out.println(sum1); System.out.println(sum2); sc.close(); } } 3번 ~ 5번 인덱스의 구간합 하지만 이렇게 모든 입력마다 구간합을 일일히 구해주는 경우에는 구간의 길이가 M이라고 하면 매 구간합을 구할 때 마다 O(M)이라는 시간이 걸리게 된다. 즉, N개의 구간 에 대해 구간의 길이가 M인 구간합을 구하는 경우 O(NM)의 시간이 걸리는 것을 알 수 있다. 예를 들어, 자연수 구간 [1, 200,000]에서 각 자연수를 시작점으로 구간의 길이가 10000인 구간합을 모두 구하라는 문제를 생각해보면 해당 연산을 바탕으로 모든 구간합을 구하는 경우 총 20억번 가량의 연산을 진행하여야 한다. *누적 합을 이용한 구간 합 구하기 -> 이러한 문제를 개선하기 위해 누적 합을 이용하여 구간 합을 구하는 방법을 사용한다면, 시간 복잡도를 O(N + M)까지 줄일 수 있다. 이 방법은 수열의 부분 합 관련 문제를 풀 때 많이 사용하던 아이디어를 가져오면 된다.

-> 일반적으로 수열 An에서 구간 [i, j]까지의 구간 합을 구하는 경우를 생각해보자.

그림과 같이 n항 까지의 합을 Sn이라고 정의한다면, 구간 [i, j]의 구간합은 Sj와 Si-1을 뺀 값을 구하여 바로 구할 수 있다. 즉, Sn까지의 구간합을 모두 구해 놓기만 한다면(O(M)) 구간 합을 구하는 연산 자체는 O(1)의 시간이면 구할 수 있기 때문에 결론적으로 O(N + M)의 시간 복잡도를 갖는 구간 합을 구하는 연산을 만들 수 있다.

-> 결론적으로 누적 합을 구하고, 그 누적합을 이용하여 주어진 구간의 합을 구하는 로직을 이용하여 구간 합을 더 빠르게 계산할 수 있다. 아래는 위와 같은 입력 값을 바탕으로 누적 합을 이용한 구간 합을 구하는 로직을 구현해 보았다.

import java.util.Scanner; public class PrefixSum { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int[] arr2 = { 1, 5, 8, 10, 24, 3, 5, 100, 99, 7 }; int a = sc.nextInt(); int b = sc.nextInt(); // 누적 합 구하기 // 배열의 크기를 + 1하는 이유는, 0번 인덱스 ~ n번 인덱스의 구간합도 구할 수 있게 만들기 위함이다. int[] prefix_sum = new int[11]; int[] prefix_sum2 = new int[11]; for (int i = 1; i < arr.length; i++) { prefix_sum[i] += prefix_sum[i - 1] + arr[i]; prefix_sum2[i] += prefix_sum2[i - 1] + arr2[i]; } // 구간 합 구하기 System.out.println(prefix_sum[b] - prefix_sum[a - 1]); System.out.println(prefix_sum2[b] - prefix_sum2[a - 1]); sc.close(); } } 3번 ~ 5번 인덱스의 구간합 ※ 주석에도 언급해 두었지만, 0번째 인덱스 ~ n번째 인덱스의 구간 합도 구할 수 있게 만들기 위해서는 배열의 크기를 하나 늘려서 앞에 0이라는 값을 추가해주어야 0번째 인덱스에 대한 구간 합도 구할 수 있다.

Cumulative Sum) 알고리즘

( 누적합 알고리즘 간단 예제 )

[ 1, 2, 3, 4, 5 ] 로 이루어준 숫자 배열에서 각 구간까지의 합을 구하는 배열 [ 1, 3, 6, 10, 15] 을 구한다고 가정해보면 아래와 같이 2가지로 구할 수 있습니다.

[ 첫번째 방법 ]

1

1+2

1+2+3

1+2+3+4

1+2+3+4+5

식으로 각 인덱스까지의 값을 반복하며 구하기.

[ 두번째 방법 ]

1

1+2

3+3

6+4

10+5

식으로 이전 인덱스까지의 누적합에 현재 인덱스의 값을 더하여 구하기.

2가지 방법을 비교해보면 두 번째 방법이 훨씬 효율적이라는 것을 알 수 있습니다.

이미지 출처 : https://sskl660.tistory.com/77

누적 합 이란 수열 An에 대해서 각 인덱스까지의 구간의 합을 구하는 것을 누적 합이라고 합니다.

시작점은 항상 첫번째 원소이며, R번째 원소까지의 합을 앞에서부터 쭉 더해오는 패턴입니다.

모든 구간에 대해서 처음부터 계산하여 단순 반복하는 것이 아니라 이전 인덱스까지의 누적합에 현재 자기 자신 값을 더하여 구현하는 것이 효과적인 방법입니다.

그렇다면 어떠한 경우에 효과적으로 누적합 알고리즘을 적용시킬 수 있을까요?

=> 배열의 [ A ~ B ] 범위의 구간 합을 구하고자 할 때, 누적합 배열을 구한 후 B 까지의 누적합에서 A-1까지의 누적합을 빼주면 [ A ~ B] 범위의 구간의 합을 구할 수 있습니다.

2. 문제 풀이

2 – 1) 백준 11659번 : 구간 합 구하기 4

function solution(input) { let answer = []; let [N, K] = input.shift().split(‘ ‘).map((dr) => Number(dr)); let matrix = input.shift().split(‘ ‘).map((dr) => Number(dr)); // 주어진 수열 // N + 1 하는 이유 => 인덱스로 접근하지 않고 순서로 접근하기 위해 let prefixSum = Array.from({ length: N + 1 }).fill(0); // 누적합 구하기 for (let i = 0; i < N; i++) { // 현재 인덱스 까지의 누적값 = 이전 인덱스의 누적값 + 현재 인덱스의 원소 값 // 순서로 접근하기 위해 1부터 넣어줌 prefixSum[i + 1] = prefixSum[i] + matrix[i]; } for (let i = 0; i < input.length; i++) { let [start, end] = input[i].split(' ').map((dr) => Number(dr)); answer.push( // end까지 누적합에서 start-1까지 누적합을 빼주면 start ~ end 까지의 구간의 합과 같음 prefixSum[end] – prefixSum[start – 1] ); } return answer.join(‘

‘) } // ‘/dev/stdin’ const input = require(‘fs’).readFileSync(‘stdin’).toString().trim().split(‘

‘); console.log(solution(input));

인덱스가 아닌 순서로 접근하기 위해 누적합의 배열을 주어진 수열의 크기보다 +1 크게 만듭니다.

누적합을 계산하여 누적합 배열에 넣어줍니다.

구간합은 end까지의 누적합에서 start-1 위치의 누적합을 빼주면 구할 수 있습니다.

2 – 2) 백준 11660번 : 구간 합 구하기 5

function solution(input) { let answer = []; let set = input.shift().split(‘ ‘); let N = Number(set[0]); // 행렬의 크기 let M = Number(set[1]); // 구간합 구해야할 구간 let matrix = Array.from(Array(N + 1), () => Array(N + 1).fill(0)); // N*N 크기의 행렬 초기화 // matrix 채워주기 – 누적합으로! for (let i = 1; i <= N; i++) { let curRow = input.shift().split(' ').map((dr) => Number(dr)); for (let j = 1; j <= curRow.length; j++) { matrix[i][j] = (matrix[i - 1][j] || 0) + (matrix[i][j - 1] || 0) - (matrix[i - 1][j - 1] || 0) // 겹치는 부분 빼주고 + curRow[j - 1]; // 자기 자신 더해주고 } } for (let i = 0; i < input.length; i++) { let curRow = input[i].split(' ').map((dr) => Number(dr)); let p1 = [curRow[0], curRow[1]]; let p2 = [curRow[2], curRow[3]]; let m_p1 = [p1[0] – 1, p2[1]]; // 빼야할 위치의 좌표1 let m_p2 = [p2[0], p1[1] – 1]; // 빼야할 위치의 좌표2 let overap_p3 = [Math.min(m_p1[0], m_p2[0]), Math.min(m_p1[1], m_p2[1])]; // 겹치는 부분의 좌표 answer.push( matrix[p2[0]][p2[1]] – matrix[m_p1[0]][m_p1[1]] – matrix[m_p2[0]][m_p2[1]] + matrix[overap_p3[0]][overap_p3[1]] // 겹치는 부분은 2번 빼줬으므로 한번 더해주기 ); } return answer.join(‘

‘) } // ‘/dev/stdin’ const input = require(‘fs’).readFileSync(‘stdin’).toString().trim().split(‘

‘); console.log(solution(input));

문제를 푼 원리 1번 문제를 푼 원리 2번

먼저 각 좌표에서 좌표끼리의 누적합을 구합니다. (누적합을 구할 때는 겹치는 부분은 2번 더해지기 때문에 한 번씩 빼줘야 합니다. – 이미지 1번 참고)

좌표 구간끼리 구간합을 구하기 위해서는 먼저 (1,1) ~ (큰 좌표)까지의 누적합에서 (1,1) ~ (큰 좌표) 좌표 구간에 포함하고 있지 않은 부분의 구간합을 빼줍니다. (겹치는 부분은 2번 빼주기 때문에 한번 더해줘야 합니다. – 이미지 2번 참고, 빨간색 네모 친 부분이 큰 좌표까지 구간에서 포함하고 있지 않은 부분이라 빼준 부분입니다. 또한 빨간색 배경으로 색칠한 부분은 2번 빼주기 때문에 한번 더해줬습니다.)

누적합 알고리즘을 바탕으로 푸는 변형 문제가 많으니 충분히 이해하고 넘어가자~

누적 합(prefix sum), 2차원 누적합(prefix sum of matrix) with java

목차

※ 배열의 인덱스는 알다시피 0부터 시작한다. 예를들어 N개의 데이터가 있다면 0번 인덱스부터 N-1번 인덱스까지 존재한다. 다만 구현에 있어 누적 합을 구현할 때는 인덱스 1번부터 사용하는게 훨씬 편하다. 즉, N개의 데이터가 존재할 경우, N+1크기의 배열을 만든 후 1번 인덱스부터 N번 인덱스까지를 사용하는게 구현하기 편하다. 따라서 이하 글에서는 후자의 방식을 사용해 서술한다.

※ 코드는 자바 기준으로 작성했지만, 어려운 코드가 아니므로 다른 언어 사용자도 문제없이 볼 수 있어요.

누적 합 (prefix sum)

1. 반복문으로 구간 합을 구할때의 문제점

특정 구간의 합을 구하는 경우를 생각해보자. 예를들어 백준의 구간 합 구하기 4 문제를 보자.

최대 10만개짜리 배열에서, 최대 10만개의 질문에 대해 i번째부터 j번째 까지의 합을 출력해야 한다. N개의 수를 입력받아둔 배열을 arr (N+1개 크기의 배열로 만들고 인덱스 0번은 사용하지 않음)이라고 할 때, 일반적으로 반복문을 사용해 답을 구한다면 다음과 같은 코드가 될 것이다.

int getRangedSum(int[] arr, int i, int j) { int sum = 0; for (int idx = i; idx <= j; idx++) { sum += arr[idx]; } return sum; } 1 <= i <= j <= N 이므로, 사실상 위 반복문은 최악의 경우 N번이 돌게되므로 getRangedSum 함수는 O(N)이 필요하다. 그리고 getRangedSum 함수가 총 M번 불릴 것이므로 O(MN) 이라는 시간복잡도가 필요하다. 이 때 N과 M은 모두 최대 100,000이므로 시간 제한인 1초(보통 1억번 정도의 시간복잡도를 가질 시 1초정도로 잡는다. NM은 100억이므로 약 100초가 소요된다.)를 아득히 넘어가게 된다. 2. 더 빠르게 구간합을 구할 방법을 생각해보자 그럼 위에서 살펴본 문제점을 개선하기 위해 속도를 빠르게 해볼 방법을 생각해보자. f(x)를 배열에서 1번째부터 x번째까지의 합이라고 해보자. 즉 위 설명에서 i는 1로 고정하고, j=x이다. 그리고 배열 arr의 i번째 수 arr[i]를 Ai 라고 하겠다. 수식으로 나타내면 아래와 같다. 그럼 이 때 배열 arr에서 a번째부터 b번째까지(a<=b) 구간의 합을 f(x)를 가지고 구할 수 있을까? 우리가 알고자 하는 값은 다음과 같다. 이 때 f(b)와 f(a-1)을 한번 봐보자. a<=b 이므로 f(b)의 중간에 Aa가 들어가게 된다. 그렇다면 f(b) - f(a-1)은 다음과 같고, 그건 우리가 알고싶은 a번째부터 b번째까지의 구간 합이 된다. 즉, a번째부터 b번째 까지의 구간합을 알기 위해 '1'에서는 Aa, Aa+1, Aa+2, ... , Ab 를 모두 알아야 했고 최악의 경우 N가지를 알아야 가능했지만, 이제는 f(b)와 f(a-1) 두가지만 알면 구할 수 있게 되었다. 즉, O(N)에서 O(2)=O(1)로 줄어들게 된다. 선형에서 상수시간으로 줄어든 것으로 엄청나게 줄어든 것이다. 3. 1부터 x까지의 합은 어떻게 구할까? 그럼 이제 '2'에서 설명했던 1번째부터 x번째까지의 합인 f(x)만 유효한 시간복잡도로 구할 수 있다면 '1'의 문제점을 해결할 수 있다. f(x)는 사실 아래 식만 이해했다면 엄청 간단하게 구할 수 있다. 즉, 1부터 x까지의 합(f(x))은 1부터 x-1까지의 합에 Ax를 더한 값임이 당연하다. f(x)를 이번엔 prefixSum[x]라고 바꿔서 부르겠다. 그럼 기존에 입력받은 배열 arr[]에 대해 아래와 같이 구해주면 된다. f(x)에 해당하는건 prefixSum[x]에 들어있는 값이 된다. int[] getPrefixSum(int N, int[] arr) { int[] prefixSum = new int[N+1]; for (int i = 1; i <= N; i++) { prefixSum[i] = prefixSum[i-1] + arr[i]; } return prefixSum; } 만약 기존 값이 딱히 필요 없다면 다음과 같이 메모리도 추가로 쓰지 않고 계산해줄 수도 있다. 이 경우엔 f(x)에 해당하는게 arr[x]가 된다. void initPrefixSum(int N, int[] arr) { for (int i = 1; i <= N; i++) { arr[i] += arr[i-1]; } } 4. 구간 합을 사용해 문제를 풀어보자. 위에서 예시로 사용했던 백준의 구간 합 구하기 4 문제를 이번엔 '2~3'에서 설명한 방식대로 한번 코드를 짜봤다. 여기서 nextInt()는 다음 정수를 입력받음을 뜻한다. // N개의 수를 입력받아 배열에 저장하면서 prefixSum도 함께 만든다. int[] arr = new int[N+1]; for (int i = 1; i <= N; i++) { int num = nextInt(); arr[i] = arr[i-1] + num; } // a와 b를 입력받아 a번째부터 b번째까지의 합을 구해 answer[]에 담는다. int[] answer = new int[M+1]; for (int i = 0; i < M; i++) { int a = nextInt(); int b = nextInt(); answer[i] = arr[b] - arr[a-1]; } 시간 복잡도가 얼마나 차이나는지 살펴보자. 우선 '1'에서와 같이 누적 합을 사용하지 않은 경우엔 O(NM)이었다. 입력받는 시간까지 치면 O(N+NM)이다. 그리고 위 코드와 같이 누적 합을 사용한 경우엔 코드에서 보다시피 O(N+M)이 된다. N과 M이 둘다 최대 100,000 이었으므로 단순 수치로 따지면 O(N+NM)=약 O(10^10), O(N+M)=O(2*10^5) 으로 엄청난 차이를 보이게 된다. 여기서 손해를 본건 없다. 만약 기존 arr 배열 외에 추가로 prefixSum 배열을 두었다면 공간복잡도에서 손해를 보게 되겠으나, 이 문제의 경우엔 입력으로 들어온 배열 자체는 이후 필요가 없으므로 바로 arr에 누적합을 계산해줘서 이득만 봤다. 맨 처음에 얘기한 바와 같이 인덱스는 원래 0부터 시작하는데, 굳이 1번부터 입력받은 이유도 코드에서 볼 수 있다. 인덱스 0번부터 데이터를 받아 사용하려면 arr[i] = arr[i-1] + num; 부분이나 answer[i] = arr[b] - arr[a-1]; 부분에서 i-1과 a-1이 0 이하일 경우를 별도로 처리해줘야 하므로 한마디로 귀찮아진다. 2차원 누적 합 (prefix sum of matrix) 1. 누적합으로 2차원 배열 누적합 구할때의 문제점 RxC 크기의 2차원 배열을 생각해보자. 여기서 이번엔 2차원으로 (1, 3)부터 (2,4)까지의 합과 같이 질문이 들어온다고 하자. (-3-2-3+1 = -7) R이 최대 1만, C의 최대도 1만, 질문의 개수 Q는 최대 10만이라고 해보자. 이 경우 '누적 합' 설명에서 '1'과 같이 시간 복잡도는 O(RC+QRC)=O(QRC)가 되므로 10^13이나 된다(처음 RC는 입력받는시간, 이후 Q개에 대해 RC번의 2중 반복문). 하지만 우린 이제 누적합 알고리즘을 쓸줄아니깐 각 행별로 누적합을 적용해서 생각해보자. 그럼 (1, 3)부터 (2,4)까지의 합은 (prefixSum[1][4] - prefixSum[1][3-1]) + (prefixSum[2][4] - prefixSum[2][3-1])과 같이 가능하다. 그렇게 해주면 O(RC+QR)=O(QR)로 줄어든다! 만약 시간복잡도가 그정도로도 충분히 통과되는 문제라면 이렇게 해줘도 상관없다. 하지만 이 설명에서는 Q는 10만, R은 1만 이므로 10^9로 1초 이내에 통과할 수 없다. 2. 2차원 누적합으로 2차원 배열을 풀어보자. 실은 좀만 생각해보면 더 빠르게 풀 수 있는 방법이 있다. prefixSum의 규칙을 좀 바꾸는거다. 1차원 배열에서 prefixSum[x]는 1번째부터 x번째까지의 합을 나타냈다. 그럼 이번엔 prefixSum[x][y]를 (1,1)부터 (x,y)까지의 합이라고 해보자. 그럼 prefixSum[x][y]를 만들었다면, 그림(그림은 입력으로 들어온 값을 나타낸다. 즉 arr[][]이다. 또한 마찬가지로 인덱스는 1번부터 사용하도록 했다.)의 빨간 부분 (3, 3)부터 (5, 5)의 합을 어떻게 구할 수 있을까? prefixSum[5][5]는 (1,1)부터 (5,5)까지의 합을 나타내므로 해당 부분에서 저 녹색 부분(prefixSum[2,5])을 빼주고, 노란 부분(prefixSum[5][2])을 빼주고, 두번 빠진 녹색과 노란색이 만나는 부분(prefixSum[2][2])를 한번 더 더해주면 될 것이다. 즉, (3,3)부터 (5,5)까지의 2차원 구간합 = prefixSum[5][5] - prefixSum[5][3-1] - prefixSum[3-1][5] + prefixSum[3-1][3-1] = 36 이다. 모든 경우에 적용될 수 있도록 (x1, y1)부터 (x2, y2) 까지(x1<=x2, y1<=y2)라고 보면 구하고자 하는 2차원 구간합은 다음과 같다. 그럼 (1,1)부터 (x,y)까지의 모든 위치의 값만 유효한 시간내에 구할 수 있다면 그 이후로는 O(1)로 구간합을 구할 수 있음을 알 수 있다. 3. (1,1)부터 (x,y)까지의 합은 어떻게 구할까? 1차원 누적합때와 마찬가지로 이번에도 (1,1)부터 (x,y)까지의 합을 유효한 시간 내에 구하는 방법을 생각해보자. 사실 '2'의 내용을 이해했다면 이건 그 반대로 구하면 된다. 입력으로 들어온 값인 arr[][] 그림을 보자. 저기서 빨간 글자 부분(4,4)에 해당하는 prefixSum[4][4]는 prefixSum[4-1][4] + prefixSum[4][4-1] - prefixSum[4-1][4-1] + arr[4][4]로 구할 수 있다. 즉, 녹색부분과 파란 부분의 누적합을 더하고, 겹치는 부분은 두번 더해졌으므로 한번 빼주고, 현재 값을 더해주면 된다. RxC 크기의 배열을 입력받았을 때를 기준으로 코드는 다음과 같다. for (int i = 1; i <= r; i++) { for (int j = 1; j <= c; j++) { prefixSum[i][j] = prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1] + arr[i][j]; } } 마찬가지로 prefixSum 배열을 추가로 만들지 않고, 기존 arr 배열의 값이 이후 필요가 없다면 arr에 바로 만들어도 된다. 코드로는 다음과 같다. for (int i = 1; i <= r; i++) { for (int j = 1; j <= c; j++) { arr[i][j] += arr[i-1][j] + arr[i][j-1] - arr[i-1][j-1]; } } arr에서 바로 prefix sum을 구하는 과정을 보자. 파란선 A->B은 B+=A를 뜻한다. 주황선 A->B는 B-=A를 뜻한다.

그럼 이제 원하는 2차원 누적합을 O(1)에 구해볼 수 있다! 예를들어 (2,2) 부터 (5,3)의 구간합을 구해보자. 좌측 arr 배열에서 직접 구하려면 2+3+2+3+2+3+2+3 = 20 이다. 우측 prefixSum에서 구하려면 30-6-5+1 = 20이다.

최종적으로 시간복잡도는 다음과 같다.

A. 2차원 구간의 합을 직접 for문을 돌려 구하는 경우 = O(RC+QRC)

B. 1차원 누적합을 각 행마다 계산하는 경우 = O(RC+QR)

C. 2차원 누적합 사용 = O(RC+Q)

입력받으면서 바로 prefixSum도 만들 수 있으므로 셋에 동일하게 존재하는 O(RC)부분을 제외하면 O(QRC)에서 O(Q)로 줄어든 셈이다.

주의점 및 관련 문제들

1. prefix sum 알고리즘 사용 시 주의점

arr[N]의 값은 결국 입력으로 주어진 모든 수의 합이 된다. 따라서 자료형에 따른 오버플로우를 주의해야한다(음수 표현범위 이하로 내려가는 것도 오버플로우). 예를들어 이 글에서 예시로 들었던 ‘구간 합 구하기 4’ 문제의 경우 N은 최대 10^5이고, 각 값은 최대 1000 이므로, 모두 합쳐봐야 최대 10^8이라서 int 자료형으로 표현해도 아무런 문제가 없었다. 이처럼 최대값을 생각해보고 자료형을 구해 누적합을 계산해줘야 한다. 만약 누적합 알고리즘을 정말 쓰고 싶은데 최대치가 자료형의 범위를 넘어간다면 보통 누적합으로 푸는 문젠 아닐꺼다. 출제자가 변태가 아닌 이상 분명 그렇겐 안낸다. 누적합 밖에 풀 방법이 생각이 안난다면 각 언어에 있는 큰 수 자료형을 쓰거나 그냥 정신건강상 파이썬

2. 풀어볼만한 PS 문제들

[ 기본 ]

백준 11659 : 구간 합 구하기 4

코드

누적합 기본 문제이다.

백준 17390 : 이건 꼭 풀어야 해!

코드 및 풀이

위 문제에서 정렬이 추가되서 아주 약간 응용되었다.

백준 11660 : 구간 합 구하기 5

1차원 누적합으로 푼 코드

2차원 누적합으로 푼 코드 및 풀이

누적합을 행이나 열의 수 만큼 적용해도 되고, 2차원 누적합으로 풀어도 되는 문제이다. 둘 다 한번 구현해보자.

[ 응용 ]

백준 10986 : 나머지 합

코드 및 풀이

누적합 응용버전이다. 한번 풀어보자.

백준 17425 : 약수의 합

코드 및 풀이

누적합은 거들뿐..

백준 2143 : 두 배열의 합

코드 및 풀이

위쪽에 적어둔 누가봐도 누적합 쓰면 쉽게 구할 수 있는 문제들과 달리 약간 거드는 느낌으로 자연스레 쓰이는 경우가 많다.

백준 23046 : 큰 수 뒤집기

코드 및 풀이

플2 문제로 많이 어렵다. 누적합인걸 안다고 바로 풀수 있는 문제는 아니다. 간단한 문제뿐 아니라 이런식으로도 누적합을 써먹을 수 있다는 의미로 넣어봤다.

백준 2835 : 인기도 조사

코드 및 풀이

세그먼트 트리 lazy propagation이 생각나는 문제지만, 의외로 누적합으로 풀 수 있긴한데 아마 누적합으로 푸는 방법을 찾긴 힘들 것이다. 풀이를 보면 이왜진 느낌으로 풀 수 있다.

누적합 계산하기

반응형

누적합(Prefix)는 언제쓰지?

누적합은 특정 수열이 주어지고, 그 수열에서 특정 구간의 값을 구하라는 쿼리가 반복적으로 들어올 때 주로 사용하게 되는 개념이다. 예를 들어 길이 n의 수열이 주어졌을 때, [0, n-1] 구간의 합이 얼마인지를 확인하라는 반복적인 쿼리가 들어올 수 있다. 이 때, [0, n-1] 구간의 합을 확인하는데 필요한 시간복잡도는 O(N)이다.

이런 쿼리가 한번만 들어오면 크게 문제는 없지만, 대부분 이런 경우는 쿼리가 반복적으로 들어온다. 이 경우, 최악의 경우 주어진 쿼리를 다 처리하는데 필요한 시간복잡도는 O(N^2)이 된다. 이 시간복잡도를 O(N)으로 처리하기 위해 필요한 개념이 누적합이다.

수열에 주어진 정보의 각각 구간의 누적합을 찾아내고, 그 값을 배열로 관리한다. 그리고 그 배열로 필요한 영역의 정보를 빠르게 접근하겠다는 개념이다.

1차원 누적합

1차원 누적합은 위의 표로 간단히 나타낼 수 있다. Sn = (A1 + A2 + … + An)으로 표현을 할 수 있다. 실제로 누적합을 구하는 배열을 관리할 때는 Sn = Sn-1 + An 점화식으로 표현하면 쉽게 구할 수 있다.

위의 이미지는 A4 ~ A6의 구간합을 구하는 것을 도식화한 그림이다. S6은 A1~A6까지의 합이고, 파란색 막대로 표현이 된다. S3은 A1~A3까지의 합이고 빨간색 막대로 표현이 된다. A4 ~ A6는 파란색 막대에서 빨간색 막대를 뺀 영역이다. A4~A6는 결국 S6 – S3으로 한 번에 구할 수 있다.

2차원 누적합

항상 1차원 배열에 대한 쿼리만 들어오면 좋겠지만, 2차원 배열이 들어오는 경우가 있다. 이 경우에는 1차원 누적합 개념에서 발전된 2차원 누적합 개념을 적용해야한다. 2차원 누적합에서 Sij는 A00 ~ Aij까지의 합을 구한 값으로 정의한다.

2차원 누적합을 구하기 위해서는 두 번의 쿼리 단계를 거쳐야 한다. 위의 이미지에서 확인이 가능하다. 먼저 왼쪽에서 오른쪽으로 1차원 누적합을 구한다. 구하면 왼쪽 테이블이 만들어진다. 왼쪽 테이블에서 세로 방향(위에서 아래 방향)으로 다시 한번 누적합을 구하게 되면 2차원 누적합을 온전히 표현하는 테이블이 만들어진다.

2차원 누적합 배열에서 노란색으로 표현된 영역의 값을 구한다고 가정해보자. 이 때는 어떻게 접근을 해야할까?

먼저 S44를 선택해준다. 왼쪽 그림의 파란색으로 표현이 가능하다. 그리고 파란색에서 빨간색으로 표현된 S42과 연두색으로 표현된 S24를 S44에서 빼주면 된다. 그런데 여기서 보라색 부분은 빨간색과 연두색 직사각형을 뺄 때 중복해서 빼진다. 이런 이유로 보라색 부분을 한번 더해주게 되면, 원하는 누적합을 구할 수 있다.

누적합의 한계

누적합은 반복적으로 누적합만 구하는 쿼리에 대해서는 빠르게 동작이 가능하다. 그렇지만 수열의 값이 변하는 경우에는 사용하기가 적절하지 않다. 수열의 값이 변할 때 마다 누적합 테이블을 다시 만들어야 하는데 1차원 테이블은 O(N), 2차원 테이블은 O(N^2)이 필요하기 때문이다.

누적합을 구해야 하는데, 구간의 요소가 자꾸 변하는 상황에서는 누적합보다는 누적합 세그먼트 트리를 구성하여 O(NlogN)으로 처리하는 것이 성능 개선에 중요한 역할을 한다.

반응형

[ 개념 ] 52. Prefix Sum(누적 합, 구간 합)

728×90

반응형

> Prefix Sum

누적 합, 구간 합

누적합 또는 구간 합을 빠르게 구하는 알고리즘입니다. 여기서 주의해야할 것은

부분 합 은 0 ~ k까지의 합을 의미하는 것이고

구간 합은 a ~ b까지의 합을 의미 합니다.

이러한 알고리즘은 어디에 쓰일까요?

아래와 같은 문제를 예시로 봅시다.

int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 배열이 존재한다. 이 때 a에서 b의 구간 합을 요구하는 쿼리 “2천만개”가 들어온다. 이러한 문제에 대해 어떻게 해결 할 것인가?

가장 쉬운 방식은 브루트 포스를 이용해 일일이 더하는 것입니다.하지만 쿼리 2천만개를 1초만에 해결해달라고 요구 했을 때, 이 코드의 시간복잡도를 보면

쿼리 2천만개 * (a에서 b의 합 구하는 for 문의 최악의 경우는 a = 0, b= 9일 때 즉, 10)

따라서 O(20,000,000 * 10) => O(200,000,000) 이므로 대략 2초입니다.

결국 시간복잡도에 걸려 요구사항을 해결하지 못하게 됩니다. 이 때문에 우리는 Prefix Sum 이라는 알고리즘을 공부하게 됩니다.

public class Main{ public Solution() { int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, ,8, 9}; int[] psum = new int[arr.length]; for (int i = 1; i < 10; i++) { psum[i] = psum[i - 1] + arr[i]; } // 쿼리 a에서 b까지의 합 구하기 int result = prefixSum(a, b); System.out.println(result); } int prefixSum(int l, int r, int[] psum) { return psum[r] - psum[l - 1]; } } 이렇게 psum이라는 누적 합 배열을 만들어 합을 미리 계산하여 저장합니다. 이렇게 된다면 쿼리는 psum[b] - psum[a - 1] 을 하게 되면 원하는 구간에서의 합을 구할 수 있습니다.!! 이 알고리즘만으로는 문제에 잘 등장하지 않고 보통 다른 알고리즘들과 함께 응용되어 나오는 경우가 많으므로 다양한 예제를 보겠습니다. - BOJ[11660] : 구간 합 구하기 5 https://www.acmicpc.net/problem/11660 /* DP와 구간합을 활용하여야 한다. 사각형의 테두리 안을 구하려면 좌상 꼭짓점:(x1, y1) , 우하 꼭짓점:(x2, y2) 일때 구간 합은 다음과 같다 DP[i][j] = DP[x2][y2] - DP[x1-1][y2] - DP[x2][y1-1] + DP[x1-1][y1-1]; */ import java.io.*; import java.util.*; public class p11660 { static int n, m, board[][], sum[][]; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); n = Integer.parseInt(st.nextToken()); m = Integer.parseInt(st.nextToken()); board = new int[n + 1][n + 1]; sum = new int[n + 1][n + 1]; for (int i = 1; i <= n; i++) { st = new StringTokenizer(br.readLine()); for (int j = 1; j <= n; j++) { board[i][j] = Integer.parseInt(st.nextToken()); } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + board[i][j]; } } for (int i = 0; i < m; i++) { st = new StringTokenizer(br.readLine()); int x1 = Integer.parseInt(st.nextToken()); int y1 = Integer.parseInt(st.nextToken()); int x2 = Integer.parseInt(st.nextToken()); int y2 = Integer.parseInt(st.nextToken()); int result = prefixSum(x1, y1, x2, y2); System.out.println(result); } } static int prefixSum(int x1, int y1, int x2, int y2) { return sum[x2][y2] - sum[x2][y1 - 1] - sum[x1 - 1][y2] + sum[x1 - 1][y1 - 1]; } } - BOJ[21318] : 피아노 체조 https://www.acmicpc.net/problem/21318 import java.io.*; import java.util.*; public class p21318 { static int n, q; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); n = Integer.parseInt(br.readLine()); int[] arr = new int[n + 1]; int[] dp = new int[n + 1]; StringTokenizer st = new StringTokenizer(br.readLine()); for (int i = 1; i <= n; i++) { arr[i] = Integer.parseInt(st.nextToken()); } for (int i = 1; i <= n; i++) { dp[i] = dp[i - 1]; if (arr[i] < arr[i - 1]) { dp[i]++; } //System.out.println("i:"+i+", dp[i]:"+dp[i]); } StringBuilder sb = new StringBuilder(); q = Integer.parseInt(br.readLine()); while (q-- > 0) { st = new StringTokenizer(br.readLine()); int x = Integer.parseInt(st.nextToken()); int y = Integer.parseInt(st.nextToken()); int result = prefixSum(x, y, dp); sb.append(result).append(”

“); } bw.write(sb.toString()); bw.flush(); bw.close(); br.close(); } static int prefixSum(int l, int r, int[] dp) { return dp[r] – dp[l]; } }

– BOJ[2015] : 수들의 합 4

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

/* 수가 자연수일 때는 투 포인터로 구할 수 있다. 하지만 이 문제는 음수와 0이 있기 때문에 구간 Map을 이용한 구간 합을 사용해야 한다. */ import java.io.*; import java.util.*; public class p2015 { static int n, k; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); n = Integer.parseInt(st.nextToken()); k = Integer.parseInt(st.nextToken()); int[] arr = new int[n + 1]; int[] psum = new int[n + 1]; st = new StringTokenizer(br.readLine()); for (int i = 1; i <= n; i++) { arr[i] = Integer.parseInt(st.nextToken()); psum[i] = psum[i - 1] + arr[i]; } // i : 0 1 2 3 // psum[i] : 2 0 2 0 HashMap map = new HashMap<>(); map.put(0, 1); long ret = 0; for(int i = 1; i <= n; i++) { ret += map.getOrDefault(psum[i] - k, 0); // key에 해당하는 value가 없으면 1, 있으면 value + 1 map.put(psum[i], map.getOrDefault(psum[i], 0) + 1); } System.out.println(ret); } } - BOJ[10986] : 나머지 합 https://www.acmicpc.net/problem/10986 /* 구간 합 <<접근방식>> 1. A % M + B % M = (A + B) % M 이므로 입력 받은 배열을 Prefix Sum 기법을 통해 누적합시킨 배열로 바꾸고 모두 M으로 나눈 나머지로 치환시킨다. 2. 그럼 이제 sum 배열이 오른쪽 항이 된 것인데, Prefix Sum 배열에서 S[i] – S[i-2]는 i-2부터 i까지의 부분합이 된다. 이를 이용하여 sum[i] = sum[j] 인 부분을 찾으면 된다. 3. ‘i부터 j까지의 합을 M으로 나눈 나머지가 0이다’라는 것이 증명 되었으니 개수를 계산하면 되는데, sum[i] = sum[j]인 부분에서 2개만 고르면 0이 되는 부분이므로 nC2가 된다. 문제의 예시를 통해 보자. 1 2 3 1 2를 Prefix Sum 배열로 바꾸고 M으로 나눈 나머지로 치환시키면 1 0 0 1 0이 된다. 이 배열의 의미가 무엇인지 파악해보자. 처음 1번째 인덱스의 0의 뜻은 처음부터 1번째까지 더하면 나누어 떨어진다는 뜻이다. 2번째 인덱스의 0의 뜻은 처음부터 2번째까지 더하면 나누어 떨어진다는 뜻이다. 3번째 인덱스의 1의 뜻은 자기와 같은 수의 0번째 인덱스의 다음 수부터 3번째까지 더하면 나누어 떨어진다는 뜻이다. 4번째 인덱스의 0의 뜻은 처음부터 4번째까지 더하면 나누어 떨어진다는 뜻과, 자기와 같은 수의 1번째 또는 2번째 인덱스의 다음 수부터 4번째까지 더하면 나누어 떨어진다는 뜻이다. 즉 자기와 같은 수들 중 2개를 선택해서 부분합을 하면 나누어 떨어진다는 것을 응용하여 0이 3개이므로 3C2 = 3, 1이 2개이므로 2C2 = 1, 총 4개에 자기까지 더해서 나누어 떨어지는 즉, 0이 3개를 더해서 7개이다. 추가적으로 자료형을 long을 써야 통과가 된다.(생각해보자.) */ import java.io.*; import java.util.*; public class p10986 { static final int MAX = 1000000 + 1; static long[] psum, cnt; static long n, m, ret; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); n = stol(st.nextToken()); m = stol(st.nextToken()); psum = new long[MAX]; cnt = new long[(int)m]; st = new StringTokenizer(br.readLine()); for (int i = 1; i <= n; i++) { long num = stol(st.nextToken()); // 부분합의 나머지를 저장 psum[i] = (psum[i - 1] + num) % m; // 나머지가 같은 부분합을 그룹화 cnt[(int) psum[i]]++; // 부분합의 나머지가 0인 경우에는 답이므로 바로 카운팅한다. if (psum[i] == 0) ret++; } // 각 부분합의 나머지가 0인 경우 같은 그룹에서 nC2를 구한다. for (int i = 0; i < m; i++) { ret += cnt[i] * (cnt[i] - 1) / 2; } System.out.println(ret); } static long stol(String s) { return Long.parseLong(s); } } 728x90 반응형

[Algorithm] 2차원 배열 부분합, 누적합 구하기

출처. 2차원 누적합, 부분합 구하기

부분 배열 합

2차원배열에서 부분 배열의 합을 구하는 방법을 알아보자. 이중 for를 이용해서 구할 수 있지만 시간복잡도가 O(n^2)이 되기 때문에 더 효율적인 방법을 찾아보자.

누적합

다음과 같은 배열이 있다고 가정하자.

arr = [ 3 , 5 , 7 , 1 , 4 ] sum_list = [ 3 , 8 , 15 , 16 , 20 ]

위 배열에서 연속된 구간의 합, 예를들어 arr[1]부터 arr[3]까지의 합을 구하려고 한다면 3개의 원소값을 참조해야한다.

이러한 점을 보완하기 위해서 누적합 수열 sum_list를 도입한다.

sum_list[i]는 arr[0]부터 arr[i]까지의 모든 원소의 합을 값으로 갖는다. 또한 arr[i]부터 arr[j]까지의 부분합은 sum_list[j] – sum_list[i-1]로 정의할 수 있다.

누적합 배열의 성질

sum_list[0] = arr[0] sum_list[i] = sum_list[i – 1] + arr[i] sum_list[n] = arr[0] + arr[1] + … + arr[n-1] + arr[n] arr[i] + arr[i + 1] + … + arr[j-1] + arr[j] = sum_list[j] – sum_list[i-1]

누적합을 활용한 빠른 부분합 계산

1차원 배열일 경우 반복문을 통한 구간합이 O(n)이고, 2차원 배열은 O(n^2)이다. 하지만 누적합 배열의 4번째 특징을 사용해서 연속된 임의의 구간의 합을 O(1)의 시간복잡도로 구할 수 있다.

2차원 누적합

위의 개념을 2차원으로 확장해보자. 이차원 배열에 대한 누적합 배열 또한 2차원 배열이 된다. 직사각형 형태의 배열에 포함되는 직사각형 구간의 원소의 합을 빠르게 구할 수 있다.

이차원 배열 a(i, j)와 누적합 배열 S(i, j)가 있다고 가정하자.

좌측 상단을 a(1, 1)이라 할 때, S(i, j)는 a(1, 1)과 a(i, j)를 양 대각 끝 꼭짓점으로 하는 직사각형 범위 면적 안의 모든 a원소의 합으로 정의된다.

따라서 a(2, 2) ~ a(3, 3)의 부분합을 구해보자.

S(3, 3) 값에서 S(1, 3) 값을 빼고, S(3, 1)값을 뺀다. 이때 S(1, 1)은 S(1, 3)과 S(3, 1)에 의해서 2번 빼진 셈이니 S(1, 1)을 더해주면 초록색 부분의 구간합이 된다.

이를 정리하면 다음과 같다.

2차원 배열 arr이 있을 때, arr[x1][y1]부터 arr[x2][y2]까지의 부분 배열의 합을 Range(x1, y1, x2, y2)라 하자. 그리고 누적합 배열 S로 다음과 같이 정의된다.

Range ( x1 , y1 , x2 , y2 ) = S ( x2 , y2 ) – S ( x1 , y2 ) – S ( x2 , y1 ) + S ( x1 , y1 )

이차원 누적합 배열 구하기

2차원 배열의 누적합 배열을 구하는 방법은 위와 같다.

a(i, j)에서 행방향으로 누적합을 구한다. 행 누적합에 대해서 열방향으로 누적합을 구한다.

arr = [ [ 0 , 1 , 1 , 0 , 0 , 1 ] , [ 0 , 0 , 0 , 0 , 0 , 1 ] , [ 1 , 0 , 1 , 0 , 1 , 1 ] ] def get_sum_list ( arr : List [ List ] ) : sum_list = [ [ sum ( arr [ i ] [ : j + 1 ] ) for j in range ( len ( arr [ 0 ] ) ) ] for i in range ( len ( arr ) ) ] for i in range ( len ( sum_list ) – 1 ) : for j in range ( len ( sum_list [ 0 ] ) ) : sum_list [ i + 1 ] [ j ] += sum_list [ i ] [ j ] return sum_list sum_list = get_sum_list ( arr ) print ( sum_list ) def accum ( S : List [ List ] , x1 : int , y1 : int , x2 : int , y2 : int ) – > int : return S [ x2 ] [ y2 ] – ( S [ x1 ] [ y2 ] + S [ x2 ] [ y1 ] ) + S [ x1 ] [ y1 ] print ( accum ( sum_list , 0 , 1 , 2 , 4 ) )

복잡도 분석

누적합은 두 단계로 나누어진다.

전처리: 누적합 배열 S를 구한다. 일차원 누적합은 O(n), 이차원 누적합은 O(n^2) 시/공간 복잡도를 갖는다. 계산: 원하는 구간의 구간합을 계산한다. 차원 관계없이 O(1)의 상수 시간 복잡도를 갖는다.

누적합의 한계

누적합의 경우 합을 구하는 도중에 원본 배열이 변하는 경우 누적합을 다시 계산해야 한다. 이 경우에는 세그먼트 트리를 사용해야한다.

[Algorithm] 누적 합(Prefix sum) 알고리즘 개념 및 코드

누적 합

Prefix sum

이번 포스팅에선 누적 합 알고리즘을 살펴보겠다.

꽤 간단하지만 알고리즘이지만, 의외로 모르는 사람이 꽤 있다.

Let’s Go

다음과 같은 문제가 있다.

n_list = [n for n in range(1, 100)] 50번째부터 – 80번째까지의 수를 더한 값을 리턴하라.

누적 합 알고리즘을 사용하지 않는다면?

단순히 리스트를 50번째부터 80번째까지 뽑아서 계산하는 방식을 가장 먼저 떠올릴 것이다.

코드로 표현하면 다음과 같다.

sum(n_list[49:79])

겉으론 문제가 없어 보이지만, 누적 합을 구하라는 요청이 많아지면 상황이 달라진다.

import time n_list = [n for n in range(1, 10000)] start = time.time() for i in range(1, 1000): sum(n_list[i:i+1000]) print(f’소요 시간: {round(time.time() – start, 5)}초’) 소요 시간: 0.01816초

1000개의 누적 합 요청이 들어올 경우, 1000번의 계산을 새롭게 해야 한다.

매번 (1) 리스트를 뽑고 (2) 그 수들을 합해야 하는 과정을 반복한다는 뜻이다.

자칫 시간 복잡도가 O(n^2)가 되어버릴 수 있기에 해결책이 필요하다.

누적 합 알고리즘을 사용한다면?

누적 합 알고리즘은 ‘그럴거면 누적 합을 미리 구해놓고 활용해라’라는 신념을 가진다.

코드로 표현하면 다음과 같다.

import itertools result = list(itertools.accumulate(n_list)) result[79] – result[48]

성능을 확인해보자.

import itertools result = list(itertools.accumulate(n_list)) start = time.time() for i in range(1, 1000): if i-2 < 0: a = 0 else: a = i-2 result[i + 999] - result[a] print(f'소요 시간: {round(time.time() - start, 5)}초') 소요 시간: 0.00047초 성능이 확실하게 향상된 것을 확인할 수 있다. 이제 문제를 풀어보자. https://www.acmicpc.net/step/48

키워드에 대한 정보 누적 합 알고리즘

다음은 Bing에서 누적 합 알고리즘 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.

이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!

사람들이 주제에 대해 자주 검색하는 키워드 코딩 테스트 \u0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum)

  • 알고리즘 대회
  • 알고리즘
  • 코딩 테스트
  • 코테
  • 투포인터
  • 구간합
  • Two Pointer
  • Prefix Sum
  • Interval Sum

코딩 #테스트 #\u0026 #알고리즘 #대회 #핵심 #노트 #- #투 #포인터(Two #Pointers), #구간 #합(Prefix #Sum)


YouTube에서 누적 합 알고리즘 주제의 다른 동영상 보기

주제에 대한 기사를 시청해 주셔서 감사합니다 코딩 테스트 \u0026 알고리즘 대회 핵심 노트 – 투 포인터(Two Pointers), 구간 합(Prefix Sum) | 누적 합 알고리즘, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.

See also  캐릭터 컨셉 아트 | 무료강좌 Character Concept Art Tutorial / 캐릭터 컨셉 아트 좀 더 쉽게 접근하기 / Free Tutorials 답을 믿으세요

Leave a Comment