일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 즉시실행함수
- 이분탐색
- react18
- 수학
- 백준
- 구현
- 동적타입언어
- 레이아웃 스래싱
- 컴포넌트 생명주기
- 이벤트 생명주기
- react
- 두 포인터
- 브루트포스
- BFS
- 값복사
- SW EA
- 분할정복
- 누적합
- next14
- 리액트
- vscode
- 슬라이딩 윈도우
- 재귀
- webpack5
- 레퍼런스복사
- 정적타입언어
- 공백찾기
- webpack
- 렌더링 최적화
- 마진 상쇄
- Today
- Total
D.JOUNG
백준 문제풀이 : (10986) 나머지 합 - c/c++ 본문
문제 : https://www.acmicpc.net/problem/10986
10986번: 나머지 합
수 N개 A1, A2, ..., AN이 주어진다. 이때, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 구하는 프로그램을 작성하시오. 즉, Ai + ... + Aj (i ≤ j) 의 합이 M으로 나누어 떨어지는 (i, j)
www.acmicpc.net
시간 제한 | 메모리 제한 | 난이도 | 알고리즘 분류 |
1초 | 256 MB | Gold 3 | 수학 / 누적합 |
문제
풀이
누적합 알고리즘을 응용해서 푸는 문제다. 하지만 시간 제한이 1초이므로 단순히 누적합 배열만 가지고 계산하려고 하면 시간 초과를 뱉어낸다. 또한 입력받는 수 A의 범위가 0<=A<=1,000,000,000 이기 때문에, 누적합 생성 및 기타 계산 시 int 형 범위를 초과할 테스트 케이스도 고려해줘야 한다.
1차원 배열이기 때문에 누적합 배열을 구하는 것은 어렵지 않다. 누적합이 무엇인 지 간단하게 설명하고 넘어가자면, 0번 째 수 부터 i 번째 수 까지의 합을 저장해놓은 배열을 뜻한다.
예를 들어 n = { 0, 1, 2, 3, 4, 5 } 라면, n의 누적합 배열은 s = { 0, 1, 3, 6, 10, 15 } 가 된다. 이렇게 누적합 배열을 계산해 놓으면 구간 별 합을 한 번의 계산으로 간단하게 알아낼 수 있다. (일반적으로 계산 편의와 가독성을 위해 배열[0]의 값은 0으로 넣어놓고, 배열의 크기를 n+1 로 잡는 편이다.)
n[1]~n[3]의 구간합 : s[3]-s[0]
n[2]~n[4]의 구간합 : s[4]-s[1]
즉, n[ i ] ~ n[ j ] 의 구간합 : s[ j ] - s[ i-1 ]
이런 식이다.
문제의 목적이 %M으로 나누어 떨어지는 구간의 개수를 구하는 것임에 주목한다. 문제에서 주어진 Test Case를 가지고 일단 손으로 한번 풀어본다.
<Test Case>
5 3
1 2 3 1 2
n 배열의 사이즈는 5, m=3.
n = { 1, 2, 3, 1, 2 } 로 주어진다.
n의 누적합 배열을 sum 이라고 하겠다.
sum = { 1, 3, 6, 7, 9 } 이다.
위 누적합 배열의 각 수를 %m 하여 '나머지 배열'이라는 새로운 배열에 저장해준다. 나머지 배열을 rem 이라고 하겠다.
rem = { 1, 0, 0, 1, 0 } 이 된다.
그럼 여기서 한 가지 규칙을 발견할 수 있다. 나머지 값들이 같은 수 끼리는 뺄샘 하였을 시 %m의 값이 0이 된다.
위 rem에서는 0번째와 3번째 자리의 값이 1이니, sum[3]-sum[0] = 6, 즉 n[1]~n[3] 의 총합은 6이며, %m 했을 시 0이 된단 뜻이다.
이를 통해 누적합 배열에서 %m의 값이 같은 수 끼리는 뺄샘 시 %m=0 의 조건을 만족한다는 사실을 알 수 있다.
그렇다면 누적합 배열에서 나머지가 0인 개수, 나머지가 1인 개수, 나머지가 2인 개수 .... 나머지가 n-1개인 개수를 각각 알아내, 같은 나머지 그룹 끼리 2개씩 선택하는 조합의 개수를 계산하면 %M으로 나누어 떨어지는 구간의 개수를 알 수 있다.
위 풀이는 s[ i ] - s[ j ] (j > 0) 일 경우의 풀이이므로 s[ i ] - s[ j ] (j==0) 인 경우의 수 까지 더해줘야 한다. 해당 경우의 수는 그냥 sum 구간합 요소 중 %m==0 인 경우의 수를 더해주기만 하면 된다.
이 풀이의 반복 횟수를 따져보자. 구간합 배열과 나머지 배열을 계산하는 것에 N번 반복이 필요하고, 나머지 그룹 별 계산 횟수는 M-1 번이다. N의 최댓값은 1,000,000 이고, M의 최댓값은 1,000 이다.
O(n) = 1,000,000 이므로 주어진 시간 1초 내에 충분히 계산 가능한 풀이다.
코드 작성
#include <stdio.h>
#include <vector>
using namespace std;
int main() {
int n, m;
scanf("%d %d", &n, &m);
long long count=0;
vector<long long> sum(1, 0); //계산 편의를 위해 sum[0]=1로 초기화.
vector<long long> remain(m, 0);
//누적합 계산과 아래 25번 줄 연산을 고려해 두 배열 모두 넉넉하게 long long으로 잡는다.
for (int nn=1 ; nn<=n ; nn++) {
int num;
scanf("%d", &num);
sum.push_back(sum[nn-1]+num); //sum[nn] = sum[nn-1] + num
remain[sum[nn]%m]++;
//%m 연산의 결과값을 인덱스 값으로 삼아, 해당 배열 칸에 개수를 저장
}
for (int i=0 ; i<m ; i++) {
if (i==0) count+=remain[i]; //위 '풀이'에서 설명한 s[ i ] - s[ j ] (j==0) 인 경우의 수
count+=( remain[i]*(remain[i]-1)/2 ); //각 나머지 구간 별 2개 조합 수 계산
}
printf("%lld", count);
return 0;
}
END.
'알고리즘 > 백준' 카테고리의 다른 글
백준 문제풀이 : (1614) 영식이의 손가락 - c/c++ (1) | 2023.12.05 |
---|---|
백준 문제풀이 : (2018) 수들의 합 5 - c/c++ (1) | 2023.11.30 |
백준 문제풀이 : (6064) 카잉 달력 - Python (3) | 2023.11.23 |
백준 문제풀이 : (1790) 수 이어 쓰기 2 - c/c++ (1) | 2023.11.21 |
백준 문제풀이 : (2022) 사다리 - c/c++ (0) | 2023.11.21 |