문제
수 N개 A1, A2, ..., AN이 주어진다. 이때, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 구하는 프로그램을 작성하시오.
즉, Ai + ... + Aj (i ≤ j) 의 합이 M으로 나누어 떨어지는 (i, j) 쌍의 개수를 구해야 한다.
입력
첫째 줄에 N과 M이 주어진다. (1 ≤ N ≤ 106, 2 ≤ M ≤ 103)
둘째 줄에 N개의 수 A1, A2, ..., AN이 주어진다. (0 ≤ Ai ≤ 109)
출력
첫째 줄에 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 출력한다.
내가 푼 풀이
첫번째 접근방법: 세그먼트 트리(시간초과)
구간합트리로 해당 구간합을 구해 나누어 떨어지는지 확인해보았다.
구간합트리의 모든노드는 모든 구간합의 경우의수가 아니였고, 구간합트리를 만들어도 모든 구간합의 경우의수를 구하려니 2중 반복문으로 시간초과가 나게되었다.
이 문제는 수학적지식을 이용해서 풀어야만 했었다. 지식의 한계를 느끼고 다른사람의 해석을 보았다.
두번째 접근방법: 합배열과 구간합
구간합배열은 이렇게 만들 수 있다.
S(1), S(2), S(3)....
S(1)은 1부터1까지, S(2)는 1부터2까지, S(3)은 1부터3까지의 구간합
A[1] | A[2] | A[3] | A[4] | A[5] | |
A | 1 | 2 | 3 | 1 | 2 |
S | 1 | 3 | 6 | 7 | 9 |
S[i] % M | 1 | 0 | 0 | 1 | 0 |
여기서 구간합배열인 S를 M으로 나누어보면 아래 배열과 같다 [1, 0, 0, 1, 0]
이를 해석해보면 구간합 [1~2], [1~3], [1~5]는 M과 나누어떨어지고, [1~1], [1~4]는 나머지 1이 남는다.
또한 구간합은 S[i~j] = S[j] - S[i-1]로 나타낼 수 있다. 예로 2에서4까지의 구간합은 S[4] - S[1]이다.
이 특징을 이용하면 S[4] - S[1] = 1 - 1 = 0이되고 이는 M으로 나누어떨어진다고 볼 수 있다.
따라서 우리는 아래처럼 종합할 수 있다.
모든 구간합의 경우의수는 아래와 같다.
1. 구간합배열S를 M으로 나누었을때의 0이되는 구간합
2. 나머지배열중 서로 나머지가 같아서 S[i~j] = S[j] - S[i-1] = 0이되는 두개의 나머지배열
1번의 경우의수는 S[2], S[3], S[5] 총 3개가 된다.
2번의 경우의 수는 같은 나머지원소중에서 2개를 뽑으면된다(S[j] - S[i-1] = 0을 성립하기 위한 S[j]와 S[i-1]이 필요하다.)
나머지가 0인것중에서 2개를 뽑는 수: ${_3}C{_2}$ -> 3*(3-1)/2 = 3
나머지가 1인것중에서 2개를 뽑는 수: ${_2}C{_2}$ -> 2*(2-1)/2 = 1
3 + 3 + 1 = 7 이 된다.
M으로 나누었을때 나머지는 0부터 M-1까지 있으므로 모두 순회하며 더해준다.
코드로 구현하면 다음과 같다.
import Foundation
// 입력받기
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
let N = input[0], M = input[1]
// 주어진배열
var arr = readLine()!.split(separator: " ").map{Int(String($0))!}
// 합배열
var S = [Int]()
var sum = 0
// 합배열을 M으로 나눈 나머지배열
var divided = [Int]()
// 나누어떨어지는 갯수
var count = 0
// 합배열 만들기
for i in arr {
sum += i
S.append(sum)
}
// 나머지 배열만들기
divided = S.map{$0 % M}
// 나머지 배열중 나머지가 0이면 그 구간합은 나누어떨어지므로 더해준다.
count = divided.filter{$0 == 0}.count
// 나머지배열중 같은 나머지의 갯수중에 2개를 고르는 경우의수를 더한다.
// S[i~j] = S[j] - S[i-1] = 0이 되는 두개의 구간합이 필요하므로
for i in 0..<M {
let num = divided.filter{$0 == i}.count
count = count + (num * (num-1))/2
}
print(count)
'코딩테스트 > 백준' 카테고리의 다른 글
BOJ-1780 종이의 개수 Swift (0) | 2024.05.03 |
---|---|
BOJ-1992 쿼드트리 Swift (0) | 2024.05.03 |
BOJ-11659 구간 합 구하기 4 Swift (1) | 2024.05.01 |
14725-개미굴 Swift (0) | 2024.04.30 |
BOJ-1043 거짓말 Swift (0) | 2024.04.29 |