본문 바로가기
백준BOJ/정렬, 투 포인터

백준 1806 부분합

by 이일아 2022. 7. 5.
반응형

https://www.acmicpc.net/problem/1806

 

1806번: 부분합

첫째 줄에 N (10 ≤ N < 100,000)과 S (0 < S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.

www.acmicpc.net

 

 


EXPLANATION

 

수열에서 연속된 수의 부분합이 S보다 크거나 같은 것 중에 최소의 길이를 가지는 걸 찾는다.

수열에서 연속된 수의 부분합을 확인하는 것이므로 두 포인터로 수열의 처음 수부터 시작해서 뒤로가며 확인한다.

부분합이 S보다 크거나 같으면 그것보다 짧은 길이의 부분합이 S보다 크거나 같은 지를 확인해야 현재까지 구한 최소길이보다 더 짧은 길이의 그러한 부분합이 존재하는 지 확인할 수 있다.

일단 최소의 길이가 나오면 수열 뒤에 또 다른 경우에 최소의 길이가 존재하든 말든 상관없다.

어차피 둘다 최소의 길이고 부분합을 이루는 수가 무엇인지 상관 없으니까.

따라서 가장 최소의 길이가 될 수 있는 1개의 부분합이 나온 이후 수열을 끝까지 돌지 않고 종료해도 된다.

그러나 수열을 확인하면서 1개의 부분합이 가능한 경우가 나오지 않으면 수열이 끝날 때 까지 1개의 부분합이 가능한지 확인해봐야 한다. 따라서, 이 경우 부분합의 마지막 수가 수열의 마지막 수일 때까지 확인하고 그 이후에 종료 된다.

 

문제 조건에 맞는 부분합을 찾으면, 그 부분합에서 더 적은 길이로도 S보다 크거나 같은 지 확인해야 하려면 그 부분합을 이루는 연속된 수 중 가장 앞에 있거나 뒤에 있는 거를 빼고 확인해야 한다. 수열의 처음부터 시작해서 마지막으로 가는 순서로 확인하고 있으니 부분합을 이루는 연속된 수 중 가장 앞에 있는 수를 뺀다.

sum -= arr[start++] (현재 부분합이 가지고 있는 수를 부분합에서 뺌)

마찬가지로,  부분합이 S보다 작으면 연속된 수를 하나 더하기 위해 부분합을 이루는 수의 마지막 수보다 index가 하나 더 큰 원소를 부분합에 더 한다.

sum += arr[++end] (현재 부분합이 가지고 있지 않는 수를 부분합에 더함)

 

물론, 문제 조건에 맞는 부분합을 찾았을 때, 부분합의 최소 길이를 비교해서 답을 갱신.

 

 

 

CODE

#include <iostream>
#include <vector>
#include <algorithm>
int INF = 100000 + 1;
using namespace std;

int main() {
    int N, S;
    cin >> N >> S;
    vector<int> arr(N);
    for (int i = 0; i < N; i++) cin >> arr[i];

    int start = 0, end = 0, sum = arr[0];    //수열의 가장 첫번째 부터 탐색 시작.
    int minLen = INF;
    while (start <= end && end < N){    //최소길이가 1인 문제 조건에 맞는 부분합이 나왔거나, 부분합의 마지막 수가 수열의 마지막 수의 index를 넘어서면 종료.
        if (sum >= S) {    // 현재 포인터의 부분합이 S보다 크거나 같으면,
            minLen = min(minLen, end - start + 1);   // 부분합의 최소 길이 비교 후 갱신.
            sum -= arr[start++];    //부분합에서 부분합이 가지고 있는 가장 앞에 수를 뺌.
        }
        else    //현재 포인터의 부분합이 S보다 작으면,
            sum += arr[++end];    //부분합에 부분합이 가지고 있는 가장 마지막 수 다음 index의 수를 더함.
    }

    if (minLen == INF) cout << 0 << "\n";    //수열을 다 돌 동안 문제 조건에 맞는 부분합을 찾지 못했으면 0출력.
    else cout << minLen << "\n";
}

 

while(A && B)는 A이고 B일 때 while문 실행. 즉, !A 혹은 !B이면 while문 종료.

 

 

 

while문 안의 sum += arr[++end]에서 out of bounds 에러가 발생하지 않는 이유가 벡터는 범위를 벗어나면 랜덤값을 자동으로 할당해주는 거 같다. 단, vector가 텅텅비지 않은 경우에만.

 

부가 설명:

vector에 할당된 메모리를 벗어난다 하더라도

다른 변수, 포인터, 클래스 객체 etc.에게 할당된 곳이 아니면 접근해도 에러는 잘 안 납니다.

소스코드상에서 뭔가를 선언한다는 건

최소한 이만큼은 자유롭게 접근&사용할 수 있게 보장해 달라고 컴파일러에게 요청하는 것이고

여길 벗어나더라도 아무도 안 쓰고 있는 공터라면

운영체제 입장에서 굳이 에러를 내지 않고 내버려두는 걸 선택할 수도 있죠.

(항상 그렇다는 건 아닙니다.)

아래 예시를 돌려보시면 정확한 값은 환경마다 심지어 매번 다를 수 있으나

일반적으로 선언한 크기인 100을 한참 넘어가서야 segfault가 날 겁니다.

하나쯤 넘어가는 건 거의 100% 확률로 문제가 없겠죠.

그럼 왜 우리가 평소 코딩할 때는 그렇게 인덱스 하나 차이로 에러가 자주 날까요?

저 예시처럼 함수 안에 vector 하나만 달랑 있는 게 아니라

수~수십 개의 변수들과 객체들이 선언돼 있으면

메모리 효율이나 여러 가지 이유로

이들의 메모리 주소를 인접하게 배정하는 경우가 많기 때문입니다.

int main() {
    vector<int> vec(100);
    for (int i = 0; i < 1000000; i++)
        cout << i << ' ' << vec[i] << endl;
    // (...생략)
    // 15441 0
    // 15442 0
    // 15443 0
    // Segmentation fault
}

 

 

반응형

'백준BOJ > 정렬, 투 포인터' 카테고리의 다른 글

백준 1450 냅색문제  (0) 2022.07.06
백준 1644 소수의 연속합  (0) 2022.07.06
백준 2470 두 용액  (0) 2022.07.04
백준 3273 두 수의 합  (0) 2022.07.04
백준 18870 좌표 압축  (0) 2022.06.04

댓글