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

 

2343번: 기타 레슨

강토는 자신의 기타 강의 동영상을 블루레이로 만들어 판매하려고 한다. 블루레이에는 총 N개의 강의가 들어가는데, 블루레이를 녹화할 때, 강의의 순서가 바뀌면 안 된다. 순서가 뒤바뀌는 경

www.acmicpc.net

 

 

[난이도] Silver1
[유형] 이분탐색

[풀이]
가능한 블루레이 크기의 최솟값은 길이가 가장 짧은 강의의 길이
이고 최댓값은 100000(N의 최댓값) * 10000 (강의 길이의 최댓값) = 10^9
입니다.

범위가 너무 크기 때문에 이분탐색을 이용해서 최솟값을 찾아주면 됩니다.

mid 값을 블루레이의 크기라고 가정 했을 때, bool check(int k) 함수를 이용해서 조건을 만족하는지 체크해주며
범위를 좁혀갈 수 있습니다.

 

#include <cstdio>
int n,m,a[100000];
bool check(int k){
    int cnt=1,sum=0;
    for(int i=0;i<n;i++){
        if(sum+a[i]>k) {
            sum=0;
            cnt++;
        }
        sum+=a[i];
    }
    return cnt <= m;
}
int main(){
    scanf("%d%d",&n,&m);
    int lo=1,hi=1e9+1;
    for(int i=0;i<n;i++) {
        scanf("%d",&a[i]);
        if(a[i]>lo) lo=a[i];
    }
    lo--;
    while(lo+1<hi){
        int mid=(lo+hi)/2;
        if(check(mid)) hi=mid;
        else lo=mid;
    }
    printf("%d",lo+1);
}

 


https://github.com/has2/Problem-Solving/blob/master/boj-solved.ac/Silver1/2343.cpp

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

 

2230번: 수 고르기

N개의 정수로 이루어진 수열 A[1], A[2], …, A[N]이 있다. 이 수열에서 두 수를 골랐을 때(같은 수일 수도 있다), 그 차이가 M 이상이면서 제일 작은 경우를 구하는 프로그램을 작성하시오. 예를 들어

www.acmicpc.net

 

 

[난이도] Gold5
[유형] 이분탐색

[풀이]
N<=100000 이므로 N^2개의 모든수의 짝을 구해보기엔 시간이 오래걸리므로
이분탐색을 이용하였습니다.
배열 A를 정렬시킨 뒤,
A[i] (i:0~N-1)를 고정시키고 A[i]~A[N-1] 의 숫자 중 조건을 가장 잘 만족시키는
숫자 A[j]를 찾으면 됩니다.
A[j]-A[i] >= M 이 조건이므로 A[i]~A[N-1]의 숫자 A[j] 중 A[j] >= A[i]+M 을 만족하는
j를 lower_bound를 이용해 찾을 수 있습니다.

아래 코드와 같이 lower_bound(a+i,a+N,a[i]+M) 는 a[i]+M 이상인 최솟값의 iterator를 반환하므로
(반환된 iterator:it)-A 의 값이 곧 index j의 값이 됩니다.
A[j]-A[i]는 정답의 후보가 될 수 있으므로 ans 값을 업데이트 해주면 됩니다.

 

#include <algorithm>
#include <cstdio>
using namespace std;
using ll = long long;
int N;
ll M,a[100001];
int main(){
    scanf("%d%lld",&N,&M);
    for(int i=0;i<N;i++) scanf("%lld",&a[i]);
    sort(a,a+N);
    int ans = 2e9;
    for(int i=0;i<N;i++){
        int idx = lower_bound(a+i,a+N,a[i]+M)-a;
        if(idx<N) ans = min(int(a[idx]-a[i]),ans);
    }
    printf("%d",ans);
}

 


https://github.com/has2/Problem-Solving/blob/master/boj-solved.ac/Gold5/2230.cpp

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

 

13397번: 구간 나누기 2

첫째 줄에 배열의 크기 N과 M이 주어진다. (1 ≤ N ≤ 5,000, 1 ≤ M ≤ N) 둘째 줄에 배열에 들어있는 수가 순서대로 주어진다. 배열에 들어있는 수는 1보다 크거나 같고, 10,000보다 작거나 같은 자연수

www.acmicpc.net

 

[난이도] Gold4
[유형] 이분 탐색

[풀이]
구간을 몇개로 나누어야 하는지, 어디를 기준으로 나누어야 하는지를 찾으려고 하면 N 제한이 5000이므로
무리가 있습니다.

이런 방법들 대신에 최종적으로 구해야하는 답인 "구간의 점수의 최댓값의 최솟값"에서 "구간의 점수의 최댓값" 을 어떤 수 m으로 고정해놓고 구간을 나눠보는 방식을 사용할 수 있습니다.

어떤 구간의 점수도 m 이상이 되도록 구간을 나누었을 때, 총 구간의 개수가 M 이하라면 이 m의 값은 정답이 될수도 있는 값이지만 최솟값은 아닙니다. 이 m의 값을 조금씩 줄여가면서 답이 될 수 있는지 확인해보는 이분탐색을 이용하면
시간내에 최적의 m 값을 찾을 수 있습니다.

 

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int N,M,a[5001];
bool check(int m){
    int cnt=0;
    int minv=a[0],maxv=a[0];
    for(int i=0;i<=N;i++){
        minv=min(minv,a[i]);
        maxv=max(maxv,a[i]);

        int diff = maxv-minv;
        if(diff<=m) continue;
        cnt++;

        minv=maxv=a[i];
    }
    return cnt<=M;
}
int sol(){
    int lo=-1,hi=10000;
    while(lo+1<hi){
        int mid=(lo+hi)/2;
        if(check(mid)) hi=mid;
        else lo=mid;
    }
    return hi;
}
int main(){
    scanf("%d%d",&N,&M);
    for(int i=0;i<N;i++) scanf("%d",&a[i]);
    a[N]=1e5;
    printf("%d",sol());
}

 


https://github.com/has2/Problem-Solving/blob/master/boj-solved.ac/Gold4/13397.cpp

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

 

1407번: 2로 몇 번 나누어질까

자연수 N이 주어지면, 자연수를 유지하면서 N을 2로 몇 번까지 나눌 수 있는지를 생각해 볼 수 있다. 즉, N의 모든 약수 중 2의 거듭제곱 꼴이면서 가장 큰 약수를 생각하는 것이다. 예를 들어 15의

www.acmicpc.net

 

 

[난이도] Gold4
[유형] 이분탐색

[풀이]
규칙성을 찾아서 수학적 수식으로 푸는것이 정해인 것 같은데
무식하게 이분탐색으로 풀었습니다.

A<= x <=B 인 x에 대해
x가 2^k * p (k>=1) 꼴로 나타내지는 것들에 대해서는 2^k 만큼 더해주면 됩니다.

결국 B를 넘지 않는 모든 2^k에 대해
A<= 2^k * p <=B 를 만족하도록 하는 p의 범위를 구해주면 됩니다.

이를 이분탐색으로 구할 수 있습니다.
A<= 2^k * left <= 2^k* right <= B
A보다 크거나 같게 만드는 left, B보다 작거나 같게 만드는 right를 이분탐색으로 각각 구하면
right-left+1 만큼이 A<= 2^k * p <=B 를 만족하는 p의 개수가 됩니다.
결국 정답 ans에 2^k * (right-left+1)를 더해주면 됩니다.

주의할 것이 k의 최댓값부터 반대로 검사를 해주면서 acnt (정답에 포함된 A<= x <= B 인 x의 개수) 를 누적해주어야 합니다. 2^k 에서 답에 누적된 x들은 2^(k-1) 에 반드시 포함되므로 이것을 빼줘야 하기 때문입니다.
그래서 right-left+1 대신 right-left+1-acnt를 사용하였습니다.

 

 

#include <cstdio>
#include <vector>
using namespace std;
using ll = long long;
ll A,B;
ll bsearch(ll v,ll lo,ll hi,ll c,bool left){
    while(lo+1<hi){
        ll mid = (lo+hi)/2;
        if(v*mid>c) hi=mid;
        else if(v*mid<=c) lo=mid;
    }
    if(left){
        if(lo*v<c) lo++;
    }else{
        if(lo*v>c) lo--;
    }
    return lo;
}
int main(){
    scanf("%lld%lld",&A,&B);

    vector<ll> vec;
    for(ll i=2;;i*=2){
       if(i>B) break;
       vec.push_back(i);
    }
    ll ans=0,acnt=0;
    for(int i=vec.size()-1;i>=0;i--){
        ll v = vec[i];
        ll lo=1,hi=(1e15+2)/v;
        ll left = bsearch(v,lo,hi,A,1);
        ll right = bsearch(v,lo,hi,B,0);
        if(left<=right && v*left>=A && v*right<=B) {
            ll cur = right-left+1-acnt;
            acnt+=cur;
            ans+=cur*v;
        }
    }
    printf("%lld",ans+(B-A+1-acnt));
}


https://github.com/has2/Problem-Solving/blob/master/boj-solved.ac/Gold4/1407.cpp

https://programmers.co.kr/learn/courses/30/lessons/12920

 

코딩테스트 연습 - 선입 선출 스케줄링

처리해야 할 동일한 작업이 n 개가 있고, 이를 처리하기 위한 CPU가 있습니다. 이 CPU는 다음과 같은 특징이 있습니다. CPU에는 여러 개의 코어가 있고, 코어별로 한 작업을 처리하는 시간이 다릅니

programmers.co.kr

 

 

[난이도] level4
[유형] 이분탐색

[풀이]
어떤 코어에서 일의 처리가 완료될때마다 그 코어에 새로운 작업을 할당해주고,
나머지 코어의 일의 남은 시간을 업데이트 하는 방식을 생각할 수 있는데 이런 방식으로는
코어의 개수 N이 10000, 처리해야할 일의 개수가 50000개 이므로 O(N*M)이 되어 시간내에
해결할 수가 없습니다.

이런 유형의 문제에는 이분탐색(파라메트릭 서치)를 이용해야 합니다.

최저시간 low=-1, 최고시간 high=200000로 두고
어떤 시간 mid가 지났을 때, 처리한 총 일의 개수 cnt를 기준으로 low 혹은 high를 mid로 업데이트해줍니다.

(코어의 수가 2개, 각각의 처리시간은 10000, 총 일의 개수가 50000일때가 시간이 가장 오래 걸리는 케이스입니다.
이때 모든 일을 처리하는데 (10000*50000)/2 시간이 걸리는데 high을 200000 정도로만 잡아도 통과를 합니다.
TC가 부족한 것 같습니다.)

처리한 총 일의 개수는 (mid(시간)/cores[i])+1 들의 합으로 구할 수 있습니다.
예를 들어 일 한개를 처리하는데 2시간이 걸리는 코어의 일처리 과정을 시간별로 보면
아래와 같이 (현재 시간을 코어의 처리시간으로 나눴을 때의 몫)+1 으로 표현됩니다.

0시간 : 1개
1시간 : 1개
2시간 : 2개
3시간 : 2게
4시간 : 3개
5시간 : 3게
6시간 : 4개
7시간 : 4개
....

이 때 목표 처리 물건 개수 n보다 적게 만드는 시간 중에 최고 시간이 lo로 수렴하도록 해야합니다.
이렇게 하면 lo+1 시간에 어떤 코어 i에 물건을 할당하면 최종적으로 n개를 처리할 수 있기 때문입니다.
0번 코어부터 N-1번 코어부터 확인하면서 lo+1 시간에 물건을 할당할 수 있는지를 검사해서 할당할 수 있으면
lo시간까지 처리한 물건의 개수에 1개씩 더해줍니다.
(lo+1)%cores[i]==0 을 만족하면 lo+1시간에 물건 할당이 가능하다는 것을 위의 일처리 과정 예시를 보면 확인할 수 있습니다.

lo가 -1로 나온 경우는 처리해야할 일의 개수가 코어 개수보다 적은 것이므로 n번째 일을 처리하는 n번 코어를 리턴해주기만 하면 됩니다.

이런 유형의 문제를 이분탐색으로 처리할 생각을 하기가 쉽지는 않지만 꽤 자주 보이는 전형적인 유형이므로
익혀두면 도움이 될 것 같습니다.

 

 

#include <vector>
#include <cstdio>
using namespace std;
int solution(int n, vector<int> cores) {
    int lo=-1,hi=2e5,mid=0;
    while(lo+1<hi){
        mid = (lo+hi)/2;
        int cnt=cores.size();
        if(mid>0) for(int i=0;i<cores.size();i++) cnt+=mid/cores[i];
        if(cnt<n) lo=mid;
        else hi=mid;
    }
    if(lo==-1) return n;
    int cnt=cores.size();
    for(int i=0;i<cores.size();i++) cnt+=lo/cores[i];
    for(int i=0;i<cores.size();i++){
        if((lo+1)%cores[i]==0) cnt++;
        if(cnt==n) return i+1;
    }
    return 0;
}



https://github.com/has2/Problem-Solving/blob/master/programmers/level4/선입_선출_스케줄링.cpp

https://programmers.co.kr/learn/courses/30/lessons/49995

 

코딩테스트 연습 - 쿠키 구입

과자를 바구니 단위로 파는 가게가 있습니다. 이 가게는 1번부터 N번까지 차례로 번호가 붙은 바구니 N개가 일렬로 나열해 놨습니다. 철수는 두 아들에게 줄 과자를 사려합니다. 첫째 아들에게는

programmers.co.kr

 

 

[난이도] level4
[유형] 이분탐색

[풀이]
N이 2000이므로 O(N^3)까지 가게되면 시간초과가 납니다.
O(N^2)이나 O(N^2logN) 알고리즘이 필요합니다.

아래 코드는 부분합+이분탐색을 이용하여 O(N^2logN) 으로 해결하였습니다.
다른 분들은 보통 투포인터를 이용한 O(N^2)으로 푼것 같습니다.

풀이는 일단 구간합 배열을 만들고 시작합니다.
sum[i] : 1~N까지 모든 원소들의 합.

그 뒤 첫번째 아들이 선택할 수 있는 모든 구간 (i~j)에 대해 둘째 아들이 선택할 수 있는 (j+1~k)가 있는지를 구할 것입니다.

구간합 배열을 이용해 sum(i,j)는 sum[j]-sum[i-1]로 구할 수 있습니다.
이제 sum[j]-sum[i-1]와 동일한 값을 같는 sum(j+1,k)를 찾아야 하는데 lower_bound(이분탐색)를 이용해 찾을 수 있습니다.

부분합 배열에서 sum(j+1,k)가 존재하는지를 찾으려면 sum[j] + sum[j] - sum[i-1] 을 lower_bound를 이용해서 찾으면 됩니다.

위의 식이 나온 이유는 아래와 같습니다.

        첫째아들   둘째아들
1+2+...[i+..+j] / [(j+1)+..+k] = sum(j+1,k)
                     [i+..+j]       = sum(i,j)

위와 같이 sum(j+1,k)와 sum(i,j)가 같아야 하기 때문에 1~j까지의 합 sum[j]에 sum(j+1,k) 인 (sum[j] - sum[i-1])를 더한 값이 부분합 배열에 존재하면 sum(j+1,k) == sum(i,j) 가 만족되어 각 아들이 가진 과자 수 sum[j] - sum[i-1]가 정답의 후보가 될 수 있습니다.

이 후보들을 이용해 최댓값을 갱신해 주면 최종 답을 구할 수 있습니다.

 

#include <vector>
#include <algorithm>
using namespace std;
int N,answer;
int solution(vector<int> cookie) {
    N=cookie.size();
    cookie.insert(cookie.begin(),0);
    vector<int> sum(N+1);
    for(int i=1;i<=N;i++) sum[i]=sum[i-1]+cookie[i];
    for(int i=1;i<=N-1;i++){
        for(int j=i;j<=N-1;j++){
            int t = 2*sum[j]-sum[i-1];
            int idx = lower_bound(sum.begin(),sum.end(),t)-sum.begin();
            if(idx<=N && sum[idx]==t) answer=max(sum[j]-sum[i-1],answer);
        }  
    }
    return answer;
}


https://github.com/has2/Problem-Solving/blob/master/programmers/level4/쿠키_구입.cpp

https://programmers.co.kr/learn/courses/30/lessons/43236

 

코딩테스트 연습 - 징검다리

출발지점부터 distance만큼 떨어진 곳에 도착지점이 있습니다. 그리고 그사이에는 바위들이 놓여있습니다. 바위 중 몇 개를 제거하려고 합니다. 예를 들어, 도착지점이 25만큼 떨어져 있고, 바위가

programmers.co.kr

 

[난이도] level4
[유형] 이분탐색

[풀이]
바위가 최대 50000개이기 때문에 이중 n개를 고른 모든 경우를 해보면 시간초과가 발생합니다.

distance가 최대 10억 이하인데 이렇게 큰 수가 주어진 경우 이분탐색을 의심해볼 수 있습니다.

1) rocks 는 오름차순으로 정렬해줍니다.
2) low를 0 high를 distance+1로 놓습니다.

3) 매 탐색에서 나오는 mid를 이용해
   "바위들간의 가장 가까운 거리는 기껏해야 mid이다"
   이렇게 정의를 하면 0 -> rocks[0] -> rocks[1]... 순으로 방문할 때
   rocks[j]-rocks[i]의 거리가 mid보다 멀면 rocks[j]는 제거된 바위로 간주하면 됩니다.

   i) 남아있는 바위의 개수가 rocks.size()-n (남아있어야할 바위의 개수) 보다 크다면,
   mid를 조금 더 큰 수를 사용해볼 수 있기 때문에 mid=lo 로 업데이트 하고 다음 시행으로 넘어갑니다.
   (잘 생각해보면 mid가 커질수록 남아있는 바위의 수는 적어지고,
   mid가 작아질수록 남아있는 바위의 수는 커지기 때문입니다.)

   ii) 남아있는 바위의 개수가 rocks.size()-n보다 더 적다면
   바위를 더 남겨야합니다. 그러려면 mid를 더 줄여서 더 간격이 좁은 바위도 포함될 수 있도록 해야합니다.
   hi=mid로 업데이트 해주고 다음 시행으로 넘어갑니다.

   위의 시행을 반복하다보면 lo와 hi가 1 차이가 나는 상황까지 수렴하게 됩니다.
   이때의 lo 값이 최적의 값이므로 이 값을 return하면 됩니다.

 

#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int solution(int distance, vector<int> rocks, int n) {
    sort(rocks.begin(),rocks.end());
    int lo=0, hi=distance+1;
    while(lo+1<hi){
        int mid=(lo+hi)/2;
        int cnt=0;
        int cur=0;
        for(int i=0;i<rocks.size();i++){
            if(rocks[i]-cur>=mid) {
                cnt++;
                cur=rocks[i];
            }
        }
        if(cnt>=rocks.size()-n) lo=mid;
        else hi=mid;
    }
    return lo;
}


https://github.com/has2/Problem-Solving/blob/master/programmers/level4/징검다리.cpp

https://programmers.co.kr/learn/courses/30/lessons/72412

 

코딩테스트 연습 - 순위 검색

["java backend junior pizza 150","python frontend senior chicken 210","python frontend senior chicken 150","cpp backend senior pizza 260","java backend junior chicken 80","python backend senior chicken 50"] ["java and backend and junior and pizza 100","pyt

programmers.co.kr

 

 

[난이도] level2
[유형] 이분탐색

[풀이]
당연히 그냥 구현 문제일줄 알았는데 info의 범위가 50000 query의 범위가 100000이나 되기 때문에
쿼리마다 모든 info를 확인하는 O(query*info)의 시간복잡도로는 효율성 테스트 통과가 불가능합니다.

지원자의 4가지 항목을 4자리 수로 변환해주는 Hashing과
어떤 점수를 넘는 지원자를 찾기 위한 binary search를 사용해주면 문제를 해결할 수 있습니다.

1) 아래와 같이 4가지 항목으로 숫자로 변환해주는 map 테이블을 만듭니다.
cpp, java, python => 1,2,3
backend, frontend => 1,2
junior, senior => 1,2
chicken, pizza => 1,2

2) 지원자의 정보 string을 분리하여 hash값을 만든 뒤 map> 타입의 map에
형태로 저장합니다. ex) mp["1111"].push_back(150)
그 뒤 map을 순회하면서 vector들을 정렬해줍니다. (binary search를 사용하기 위함입니다.)

3) query를 순회하면서 string을 쪼개서 만들 수 있는 모든 hash를 만들어줍니다.
저는 vector 4개를 선언하여 "-" 가 아닌 경우에는 그에 맞는 string의 변환값을 넣어주었고,
"-"인 경우에는 1,2 (언어의 경우 3까지) 모두 되므로 모두 vector에 넣어준 뒤
4중 for문을 돌려서 만들 수 있는 모든 hash를 만들어주었습니다.
query가 -,-,-,- 이어도 최대 3x2x2x2=24개 이기때문에 많은 시간이 들지 않습니다.

각 2)에서 저장한 hash key에 맞는 점수들이 저장되어있는 vector를 map에서 가져와
binary search를 통해 조건을 만족하는 가장 적은 점수의 지원자의 iterator를 가져옵니다.
mp[key].end()-it를 통해 조건을 만족하는 모든 지원자를 구할 수 있습니다.
이 값을 cnt 변수에 카운트해주면 최종 cnt 값이 현재 query의 정답입니다.

 

#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <iostream>
using namespace std;

map<string,string> table;

vector<string> split(string s,string splitter){
    vector<string> ret;
    s+=splitter;
    int idx;
    while((idx=s.find(splitter)) != string::npos){
        string tmp = s.substr(0,idx);
        ret.push_back(tmp);
        //cout << tmp << '\n';
        s=s.substr(idx+splitter.size());
    }    
    return ret;
}

vector<int> solution(vector<string> info, vector<string> query) {
    vector<int> answer;

    table["cpp"]="1",table["java"]="2",table["python"]="3";
    table["backend"]="1",table["frontend"]="2";
    table["junior"]="1",table["senior"]="2";
    table["chicken"]="1",table["pizza"]="2";

    map<string,vector<int>> mp;
    for(auto s : info) {
        auto v = split(s," ");
        string hash;
        for(int i=0;i<v.size()-1;i++){
            hash+=table[v[i]];
        }
        mp[hash].push_back(stoi(v.back()));
    }
    for(auto& p : mp){
        sort(p.second.begin(),p.second.end());
    }

    int qnum=0;
    for(auto q : query){
        vector<string> sq = split(q," and ");
        vector<string> b = split(sq.back()," ");
        sq.pop_back();
        sq.insert(sq.end(),b.begin(),b.end());

        vector<string> v[4];

        for(int i=0;i<4;i++){
            if(sq[i]!="-") v[i] = {table[sq[i]]};
            else {
                v[i] = {"1","2"};
                if(i==0) v[i].push_back("3");
            }
        }

        int cnt=0;
        for(auto c1 : v[0]){
            for(auto c2 : v[1]){
                for(auto c3 : v[2]){
                    for(auto c4: v[3]){
                        string key = c1+c2+c3+c4;
                        int target=stoi(sq[4]);
                        auto it = lower_bound(mp[key].begin(),mp[key].end(),target);
                        cnt+=mp[key].end()-it;
                    }
                }
            }
        }
        answer.push_back(cnt);

    }
    return answer;
}

 

https://github.com/has2/Problem-Solving/blob/master/programmers/level2/순위 검색.cpp

+ Recent posts