728x90
728x90
문제
수 N개 이 주어진다. 이때, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 구하는 프로그램을 작성하시오.
즉, (i ≤ j) 의 합이 M으로 나누어 떨어지는 (i, j) 쌍의 개수를 구해야 한다.
입력
첫째 줄에 N과 M이 주어진다. ()
둘째 줄에 N개의 수 이 주어진다. ()
출력
첫째 줄에 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 출력한다.
예제 입력 1
5 3 1 2 3 1 2
예제 출력 1
7
알고리즘 분류
- 수학
- 누적 합
문제 출처
https://www.acmicpc.net/problem/10986
10986번: 나머지 합
수 N개 A1, A2, ..., AN이 주어진다. 이때, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 구하는 프로그램을 작성하시오. 즉, Ai + ... + Aj (i ≤ j) 의 합이 M으로 나누어 떨어지는 (i, j)
www.acmicpc.net
문제 해결 방법
- 구간 합 알고리즘을 이용하여 풀 수 있는 문제였다.
구간 합을 구하는 공식을 문제 해결에 활용하기
- 이 문제는 구간 의 구간 합을
M
으로 나눈 나머지가 0인 구간의 개수를 출력하도록 만드는 문제이다. - 구간 의 구간 합은
pSum[j] - pSum[i - 1]
으로 구할 수 있고, 나머지 연산은 모듈러(%
) 연산자를 이용하여 구할 수 있다. 따라서 다음과 같은 수학식을 작성할 수 있다.
- 모듈러(
%
) 연산자는 분배 법칙이 성립하므로, 다음과 같이 식을 전개시킬 수 있다.
- 따라서 다음과 같은 식이 성립하게 된다.
pSum[j]
: 첫 번째 인덱스부터j
번째까지의 구간 합(부분 합)pSum[i - 1]
: 첫 번째 인덱스부터i - 1
번째까지의 구간 합(부분 합)
- 이 식의 의미는 첫 번째 인덱스부터
j
번째까지의 구간 합을M
으로 나눈 후의 나머지 값과 첫 번째 인덱스부터i - 1
번째까지의 구간 합을M
으로 나눈 후의 나머지 값이 같음을 의미한다.- 나머지 값으로는
{0, 1, ..., M - 1}
이 될 수 있다.
- 나머지 값으로는
예제를 이용하여 문제 풀기
- 문제에서 주어진
예제 입력 1
을 이용하여 문제를 이해해보자. (N
: 5,M
: 3)
i | 0 | 1 | 2 | 3 | 4 | 5 |
num | - | 1 | 2 | 3 | 1 | 2 |
pSum[i] | - | 1 | 3 | 6 | 7 | 9 |
pSum[i] % M |
- | 1 | 0 | 0 | 1 | 0 |
-
pSum[i] % M
의 결과값을 크기가 1,001인cnt
배열에 저장해줄 것이다.- 문제에서
M
의 입력 범위로 가 주어졌으므로,cnt
배열의 크기는 넉넉하게 1,001로 지정해준다.
- 문제에서
for (int i = 1; i <= n; i++) { cin >> num; pSum[i] = pSum[i - 1] + num; // 구간 합 구하기 cnt[pSum[i] % M]++; }
cnt
배열에는 구간합pSum[i]
을M
으로 나눈 나머지 값들이 들어 있으며, 다음과 같이 값들이 정리될 것이다.
i | 0 | 1 | 2 |
cnt[i] | 3 | 2 | 0 |
경우 | {1, 2}, {1, 2, 3}, {1, 2, 3, 1, 2} | {1}, {1, 2, 3, 1} | { } |
①
- 이제 을 만족시키는 경우의 수를 찾아야 한다.
- 나머지가 0인 경우의 수(
cnt[0]
)에서 2가지 방법을 뽑는 경우()와 나머지가 1인 경우의 수(cnt[1]
)에서 2가지 방법을 뽑는 경우(), 그리고 나머지가 2인 경우의 수(cnt[2]
)에서 2가지 방법을 뽑는 경우()를 모두 더 해준다.
ull Combination(ull n) { return (n * (n - 1)) / 2; } ull Solution(int n, int m) { // ... for (int i = 0; i < m; i++) { answer += Combination(cnt[i]); // nC2 구하기 } }
참고
- :
[{1, 2}, {1, 2, 3}]
,[{1, 2, 3}, {1, 2, 3, 1, 2}]
[{1, 2}, {1, 2, 3, 1, 2}]
- :
[{1}, {1, 2, 3, 1}]
- :
[{ }]
- 각각의 경우의 구간 합을
M
으로 나눈 후의 나머지 값이 서로 비슷하므로 을 만족시킨다. - 조합(Combination) 공식은 이며, 이는 개 중에서 순서를 고려하지 않고 개를 뽑는 경우의 수를 의미한다.
- 이다.
②
- 추가적으로
pSum[a] % M = 0
인 경우는 단독으로M
으로 나누어 떨어지는 경우이다. ([{1, 2}]
,[{1, 2, 3}]
,[{1, 2, 3, 1, 2}]
) - 따라서 결과 값(
answer
: 4)에 추가적으로 단독으로M
으로 나누어 떨어지는 경우인 3(cnt[0]
)을 더해줘야 한다.
ull Solution(int n, int m) { for (int i = 1; i <= n; i++) { cin >> num; pSum[i] = pSum[i - 1] + num; // 구간 합 구하기 cnt[pSum[i] % M]++; } for (int i = 0; i < m; i++) { answer += Combination(cnt[i]); // nC2 구하기 } return cnt[0] + answer; }
코드
#include <iostream> using namespace std; using ull = unsigned long long; int N, M, num; ull pSum[1000001]; ull cnt[1001]; ull answer; void Input() { cin >> N >> M; } ull Combination(ull n) { return (n * (n - 1)) / 2; } ull Solution(int n, int m) { for (int i = 1; i <= n; i++) { cin >> num; pSum[i] = pSum[i - 1] + num; // 구간 합 구하기 cnt[pSum[i] % M]++; } for (int i = 0; i < m; i++) { answer += Combination(cnt[i]); // nC2 구하기 } return cnt[0] + answer; } void Output() { cout << Solution(N, M) << '\n'; } void Solve() { Input(); Output(); } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); Solve(); return 0; }
채점 결과

참고
- [단계별로 풀어보기] > [누적 합]
- 골드III
조합(Combination)
- 개 중에서 순서를 고려하지 않고 개를 뽑는 경우의 수
728x90
728x90
'Problem Solving > BOJ' 카테고리의 다른 글
[BOJ-10807][C++] 개수 세기 (0) | 2023.02.03 |
---|---|
[BOJ-25682][C++] 체스판 다시 칠하기 2 (0) | 2023.01.26 |
[BOJ-2167][C++] 2차원 배열의 합 (0) | 2023.01.24 |
[BOJ-11660][C++] 구간 합 구하기 5 (2) | 2023.01.24 |
[BOJ-16139][C++] 인간-컴퓨터 상호작용 (0) | 2023.01.10 |
[BOJ-12865][C++] 평범한 배낭 (0) | 2023.01.09 |
[BOJ-9251][C++] LCS (0) | 2023.01.09 |
[BOJ-2565][C++] 전깃줄 (0) | 2023.01.04 |