Loading [MathJax]/jax/output/HTML-CSS/jax.js

ABOUT ME

Today
Yesterday
Total
  • 1498-B, *1300
    코드포스 2021. 8. 16. 21:54
     

    Problem - 1498B - Codeforces

     

    codeforces.com

    문제

    • 높이가 1이고 너비가 2의 거듭제곱 형태(wi=2k)인 블록 n(1n105)개가 주어진다
    • 높이가 1이고 너비가 w인 박스가 있을 때, 모든 블록을 담으려면 최소 몇 개의 박스가 필요한가?
      (단, wmax(wi)

     

    풀이1

    Greedy
    너비가 큰 블록부터 상자에 넣는다.
    블록이 들어간 상자들 중 남는 자리가 있으면 그 곳에 채워넣고, 없으면 새로운 상자에 블록을 넣는다

     

    Claim 1
    wi가 2의 거듭제곱 꼴이고 w1 부터 wn이 증가할때
    w1+w2++wn>w0, w1w0 i[1, n) s.t. w1+w2++wi=w0


    w1+w2++wn>2, w12에 대해
    w1=2이면 i=1 에서 준 명제가 성립한다
    w1<2이면 w1+w2++wn=(1+1)+1++1 이므로 성립한다

    w0=2k(k1)일 때 준 명제가 성립한다고 가정하자. 그러면
    w1+w2++wn>2k+1, w12k+1에 대해
    i) w1=2k+1이면 i=1에서 준 명제가 성립하고
    ii) w1<2k+1 i.e. wi2k일 때 
    w1+w2++wj=2k( 가정)
    wi+1+w2++wk=2k, k<n( 가정)
    따라서 i=k에서 준 명제가 성립한다

    수학적 귀납법에 의해 준 명제는 성립한다□

     

    Claim 2
    최적 알고리즘을 사용하여 상자에 넣은 블록들의 자리를
    사용한 상자의 높이 변화 없이 서로 바꿔서 greedy 알고리즘 결과와 같게 만들 수 있다

    pf
    최적 알고리즘의 각 상자를 내림차순으로 정렬하자
    또한 편의상 greedy 알고리즘에서 상자에 블록을 넣을 때 항상 왼쪽부터 채워 넣는다고 하자. 그러면
    최적 알고리즘과 greedy 알고리즘 모두 각 층마다 왼쪽 블록이 오른쪽 블록보다 크거나 같은 상태이다

    두 알고리즘의 해를 동시에 아래부터 위쪽으로, 왼쪽부터 오른쪽으로 살펴보자
    처음으로 나오는 다른 크기의 블록을 Bo, Bg 이라고 하면

    i) Bo<Bg


    Bo를 포함한 오른쪽에 있는(물론 없을수도 있다) 모든 블록의 너비합은
    가. Bg의 너비보다 크다
    - Claim 1 에 의해 앞부분 일부의 너비합은 Bg와 같다. 이 부분을 위쪽에 있는 Bg와 바꾼다
    나. Bg의 너비보다 작거나 같다
    - 전부 위쪽에 있는 Bg와 바꾼다

    ii) Bo>Bg
    이런 경우는 존재하지 않는다.
    Bo가 더 크다면 greedy 알고리즘에서 Bg보다 Bo를 먼저 배정한다
    따라서 Bo를 배정할 때에는 Bg 자리는 비어있거나 Bo 보다 큰 블록이 차지하고 있어야한다□

    priority queue, O(nlog n)

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <bits/stdc++.h>
    #define endl "\n"
    #define loop(i, n) for(int i = 1; i <= n; i++)
    using namespace std;
     
    int n, w;
    priority_queue<intvector<int>, less<int> > pq;
     
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0), cout.tie(0);
     
        int t;
        cin >> t;
        while(t--){
            cin >> n >> w;
            int tmp;
            loop(i, n) {
                cin >> tmp;
                pq.push(tmp);
            }
     
            priority_queue<intvector<int>, less<int> > basket;
            basket.push(w);
            while(!pq.empty()){
                int b = pq.top(); pq.pop();
                int remain = basket.top(); basket.pop();
                if(remain >= b) basket.push(remain-b);
                else{
                    basket.push(remain);
                    basket.push(w-b);
                }
            }
            cout << basket.size() << endl;
        }
     
        return 0;

     

    풀이2

    Greedy
    상자에 가장 큰 블록을 넣는다
    남은 공간에 들어갈 수 있는 가장 큰 블록을 찾아 넣는다. 없으면 새로운 상자에 그 블록을 넣는다(반복)

     

    Claim 3
    최적 알고리즘을 사용하여 상자에 넣은 블록들의 자리를
    사용한 상자의 높이 변화 없이 서로 바꿔서 greedy 알고리즘 결과와 같게 만들 수 있다

    pf
    최적 알고리즘의 각 상자를 내림차순으로 정렬하자
    또한 편의상 greedy 알고리즘에서 상자에 블록을 넣을 때 항상 왼쪽부터 채워 넣는다고 하자. 그러면
    최적 알고리즘과 greedy 알고리즘 모두 각 층마다 왼쪽 블록이 오른쪽 블록보다 크거나 같은 상태이다

    두 알고리즘의 해를 동시에 아래부터 위쪽으로, 왼쪽부터 오른쪽으로 살펴보자
    처음으로 나오는 다른 크기의 블록을 Bo, Bg 이라고 하면

    i) Bo<Bg


    Bo를 포함한 오른쪽에 있는(물론 없을수도 있다) 모든 블록의 너비합은
    가. Bg의 너비보다 크다
    - Claim 1 에 의해 앞부분 일부의 너비합은 Bg와 같다. 이 부분을 위쪽에 있는 Bg와 바꾼다
    나. Bg의 너비보다 작거나 같다
    - 전부 위쪽에 있는 Bg와 바꾼다

    ii) Bo>Bg
    이런 경우는 존재하지 않는다
    greedy 알고리즘에서 Bg를 배열할 시점에 Bo 블록도 아직 배정하지 않은 상태이다
    greedy 알고리즘은 큰 블록을 우선으로 배정하므로
    Bo가 더 큰 블록이었다면 Bo을 배정했을 것이다□

    bitmasks, O(nlog wmax)

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #include <bits/stdc++.h>
    #define endl "\n"
    #define loop(i, n) for(int i = 1; i <= n; i++)
    using namespace std;
     
    int n, w;
     
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0), cout.tie(0);
     
        int t;
        cin >> t;
        while(t--){
            cin >> n >> w;
            vector<int> cnt(20);
            int tmp;
            loop(i, n){
                cin >> tmp;
                cnt[log2(tmp)]++;
            }
     
            int res = 1, remain = w;
            loop(i, n){
                int largest = -1;
     
                for(int size = 19size >= 0size--){
                    if(cnt[size&& (1 << size<= remain) {
                        largest = size;
                        break;
                    }
                }
     
                if(largest == -1){
                    res++;
                    remain = w;
                    for(int size = 19size >= 0size--){
                        if(cnt[size&& (1 << size<= remain) {
                            largest = size;
                            break;
                        }
                    }
                }
     
                cnt[largest]--;
                remain -= 1 << largest;
            }
     
            cout << res << endl;
     
        }
     
        return 0;

     

    multiset, O(nlog n)

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #include <bits/stdc++.h>
    #define endl "\n"
    #define loop(i, n) for(int i = 1; i <= n; i++)
    using namespace std;
     
    int n, w;
     
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0), cout.tie(0);
     
        int t;
        cin >> t;
        while(t--){
            cin >> n >> w;
            multiset<int> ms;
            int tmp;
            loop(i, n){
                cin >> tmp;
                ms.insert(tmp);
            }
     
            int res = 1, remain = w;
            while(!ms.empty()){
                auto it = ms.upper_bound(remain);
                if(it != ms.begin()){
                    it--;
                    remain -= *it;
                    ms.erase(it);
                }
                else{
                    res++;
                    remain = w;
                }
            }
     
            cout << res << endl;
     
        }
     
        return 0;

     

    풀이3

    상자의 개수 cnt가 주어졌을 때 모든 블록을 넣을 수 있는지 검사할 수 있다면
    이진탐색을 통해 cnt의 값을 구해낼 수 있다

    먼저, 동일한 크기의 블록만을 상자에 넣는 경우를 생각하자
    블록의 크기가 4일때 하나의 상자에는 최대 w4개 들어가므로
    상자의 개수가 cnt개이면 최대 수용할 수 있는 블록의 개수는
    suff4=cntw4

    따라서 크기가 4인 블록의 개수가 suff4보다 크면 cnt를 늘려야 한다

    한편, 크기가 4인 블록을 모두 넣을 수 있을때 크기가 2인 블록까지 추가하여 넣을 수 있는지 살펴보자
    크기가 4인 블록은 크기가 2인 블록 두개를 붙여놓은 것으로 생각한다
    그러면
    suff2=cntw2

    따라서 2×(4개수)+(2개수)가 suff2보다 작으면 크기가 4인 블록과 2인 블록 모두를 상자에 담을 수 있다

    ※ 크기가 4인 블록이 들어가는지 반드시 먼저 확인해야 한다.
    이는 크기가 4인 블록이 짤리지 않았음을 보장해준다
    (4블록은 다 들어가므로 그대로 두고 2를 채워 넣는다고 생각)

    binary search, O(n+log wmaxlog n)

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #include <bits/stdc++.h>
    #define endl "\n"
    #define loop(i, n) for(int i = 1; i <= n; i++)
    using namespace std;
     
    array<int20> arr;
    int n, w;
     
    bool valid(int cnt)
    {
        for(int i = 19; i >= 0; i--){
            long long suff = 1LL*cnt*(w/(1 << i));
            if(arr[i] > suff) return false;
        }
        return true;
    }
     
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0), cout.tie(0);
     
        int t;
        cin >> t;
        while(t--){
            cin >> n >> w;
     
            for(int i = 0; i < 20; i++) arr[i] = 0;
     
            int tmp;
            loop(i, n){
                cin >> tmp;
                arr[log2(tmp)]++;
            }
     
            for(int i = 18; i >= 0; i--) arr[i] += 2*arr[i+1];
     
            int lo = 1, hi = n;
            int ans = -1;
            while(lo <= hi){
                int mid = (lo + hi) / 2;
                if(valid(mid)){
                    hi = mid - 1;
                    ans = mid;
                }
                else lo = mid + 1;
            }
     
            cout << ans << endl;
     
        }
     
        return 0;

    '코드포스' 카테고리의 다른 글

    1283-D, *1800  (0) 2021.09.16
    1560-A, *800  (0) 2021.08.30
    1553-C, *1200  (0) 2021.08.15
    1553-D, *1500  (0) 2021.08.15
    1554-A, *800  (0) 2021.08.15

    댓글

Designed by Tistory.