Processing math: 100%

알고리즘

[BOJ 3673] 나눌 수 있는 부분 수열

dldyou 2024. 3. 28. 13:00

양의 정수로 이루어진 수열에서 '연속하는 부분 수열의 합이 d로 나누어 떨어지는 것'의 개수를 구하는 문제이다.

 

단순하게 생각해 보면 모든 구간에 대해 확인을 해보면 된다.

하지만 이는 O(n2)이 걸리며, 문제에서 n의 제한은 50 000까지이다. 

 

다른 방법을 생각해보자. 누적합을 이용해 볼 수 있을까?

Ai부터 Aj까지의 합을 7로 나눈 나머지가 3이라고 하자.

그리고 Ak까지의 합을 7로 나눈 나머지를 4라고 하자. (i<j<k)

그렇다면 Aj+1부터 Ak까지의 합을 7로 나눈 나머지는 몇이 될까?

 

1이 될 것이다. A mod MT1이고 B mod MT2이면 (A+B) mod M(T1+T2) mod M의 성질을 만족하기 때문이다. 

 

위의 상황을 바꾸어보면, Ai부터 Aj까지의 나머지와 Ak까지의 나머지가 같을 때, Aj+1부터 Ak까지의 나머지가 0으로 d로 나누어 떨어지게 된다. 

 

즉, 나머지가 동일한 것끼리 선택을 하면 해당 구간은 나누어 떨어지게 된다. 그렇기에 나머지의 개수를 세어주고, nC2로 나머지가 같은 것 중 2개를 고른 경우의 수를 계속해서 답에 더해주면 된다.

 

이때, 아무것도 더하지 않은 상태를 추가해야 해서 나머지가 0인 개수를 1개로 초기화해 놓고 시작하는 것을 유의하자. 또한, 답과 계산과정에서 long long으로 넘어갈 수 있음을 유의하자. 

#define ll long long
#define MAXN 50001

ll c, d, n;
ll cnt[1000001];
ll A[MAXN], ans;

int main(void) {
    fastio;
    cin >> c;
    while (c--) {
        cin >> d >> n;
        memset(cnt, 0, sizeof(cnt)); cnt[0] = 1;
        for (int i = 1; i <= n; i++) {
            cin >> A[i];
            A[i] = (A[i] + A[i - 1]) % d;
            cnt[A[i]]++;
        }

        ans = 0;
        for (int i = 0; i < d; i++) {
            if (cnt[i] > 1)
                ans += cnt[i] * (cnt[i] - 1) / 2;
        }
        cout << ans << "\n";
    }
    return 0;
}

'알고리즘' 카테고리의 다른 글

[BOJ 2992] 크면서 작은 수  (0) 2024.03.29
[BOJ 1448] 삼각형 만들기  (0) 2024.03.28
[BOJ 20310] 타노스  (0) 2024.03.26
[BOJ 4900] 7 더하기  (0) 2024.03.26
[BOJ 17215] 볼링 점수 계산  (0) 2024.03.26