Problem Solving/백준 1일 1커밋
백준 10986 나머지 합 - C++
HWAN JJ
2022. 2. 3. 15:59
https://www.acmicpc.net/problem/10986
10986번: 나머지 합
수 N개 A1, A2, ..., AN이 주어진다. 이때, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 구하는 프로그램을 작성하시오. 즉, Ai + ... + Aj (i ≤ j) 의 합이 M으로 나누어 떨어지는 (i, j)
www.acmicpc.net
<문제>
수 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으로 나누어 떨어지는 구간의 개수를 출력한다.
<풀이>
연속된 부분 구간을 다루는 가장 잘 알려져 있는 방법은 prefix sum을 이용하는 것이다.
구간 [i, j]를 볼 때는 pre[j]와 pre[i-1]을 살펴봄으로써 구간에 대한 정보를 얻는다.
여기서 pre[x]는 앞에서부터 x까지의 부분 구간의 정보이다.
여기서 우리가 알 필요가 있는 정보는 정확한 구간의 합이 아닌
부분 구간의 합의 "나머지"이다.
추가 배열을 만들어서, 0~i번째 원소까지의 합의 나머지가 0 ~ m-1 사이에 존재하므로,
cnt[원소 합의 나머지] 를 증가시키고, 이를 고려하여 cnt를 증가시키면 된다.
<후기>
처음에는 아무 생각 없이 presum을 이용하긴 했지만, 나머지에 대한 생각을 하지 않아서 O(n^2)로 해결하려 했는데
어느정도 개선을 해도 시간 초과가 나와서, 나머지가 0 ~ m -1 사이이고 m이 최대 1000 이라는 사실을 보고 다시 접근했다.
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
int cnt[1010] = {0, };
// 백준 10986번 나머지 합 문제
// N개의 수가 주어지고, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수
// 연속된 부분 구간을 다루는 가장 잘 알려져 있는 방법은 prefix sum을 이용하는 것이다.
// 구간 [i, j]를 볼 때는 pre[j]와 pre[i-1]을 살펴봄으로써 구간에 대한 정보를 얻는다.
// 여기서 pre[x]는 앞에서부터 x까지의 부분 구간의 정보이다.
// 여기서 우리가 알 필요가 있는 정보는 정확한 구간의 합이 아닌
// 부분 구간의 합의 "나머지"이다.
// 추가 배열을 만들어서, 0~i번째 원소까지의 합의 나머지가 0 ~ m-1 사이에 존재하므로,
// cnt[원소 합의 나머지] 를 증가시키고, 이를 고려하여 cnt를 증가시키면 된다.
int main(void){
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, m, a;
ll presum = 0, ans = 0;
cin >> n >> m;
// 입력으로 m의 배수가 들어오는 경우
cnt[0]++;
for(int i=0;i<n;i++){
cin >> a;
presum += a;
presum %= m;
// 지금까지 presum의 나머지가 나온 구간 합들을 빼면 개수 증가 가능
ans += cnt[presum];
// presum 하나 추가
cnt[presum]++;
}
cout << ans;
return 0;
}