문제

편의점 주인인 동빈이는 N개의 동전을 가지고 있다.
N개의 동전을 이용하여 만들 수 없는 양의 정수 금액 중 최솟값을 구하라.

예를 들어,

N = 5이고, 각 동전이 각각 3원, 2원, 1원, 1원, 9원짜리 동전이라고 가정하자. 이때 동빈이가 만들 수 없는 양의 정수 금액 중 최솟값은 8원이다.

N = 3이고 각 동전이 각각 3원, 5원, 7원이면, 만들 수 없는 양의 정수 금액 중 최솟값은 1원이다.

입력 

  • 첫째 줄에는 동전의 개수를 나타내는 양의 정수 N이 주어진다. ( 1 <= N 1,000)
  • 둘째 줄에는 각 동전의 화폐 단위를 나타내는 N개의 자연수가 주어지며 각 자연수는 공백으로 구분된다. 각 화폐 단위는 1,000,000 이하의 자연수이다.

출력 

  • 첫째 줄에 주어진 동전들로 만들 수 없는 양의 정수 금액 중 최솟값을 출력한다.

입력 예시

입력


3 2 1 1 9
8

느낀점

이 문제는 처음 보면 그리디 알고리즘이라고 생각하기 힘든 문제였다.

하지만 알고나면 너무 쉬운 문제다.

하나의 유형이라고 생각하고 외워두면 좋을 것 같다.

 

문제를 풀어내는 사고방식은 다음과 같다.

작은 동전부터 탐색하면서, 만들고자 하는 금액(target)을 아직 이용하지 않고 가장 작은 금액의 동전이 만들고자 하는 금액 이하이면 해당 금액을 그 전에 이용한 동전과 조합해 만들 수 있다.

그 전에 이용했던 동전들로 1~target-1의 금액을 만들 수 있고 이용하지 않은 가장 작은 금액의 동전이 target 보다 작다면
     이용하지 않은 가장 작은 금액의 동전 ( <= target) + (1~target-1)  target

이 성립한다.

따라서 동전들을 정렬한 후, 하나씩 뽑으면서 다음 target을 업데이트해주면 된다.

 

#include <iostream>
#include <vector>

using namespace std;

int n;

vector<int> v;
int main(void) {
    cin >> n;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        v.push_back(x);
    }

    // 1. 정렬
    sort(v.begin(), v.end());

    // 2. 첫번째 target은 1, 이용하기 전의 가장 작은 동전이 target이하여야 target을 만들 수 있다.
    int target = 1;
    for (int i = 0; i < n; i++) {
        if (v[i] <= target) {
            target += v[i];
        }
        else {
            // 만들 수 없는 금액 종료
            break;
        }
    }

    cout << target << "\n";

    return 0 ;
}

문제

올해 ACM-ICPC 대전 인터넷 예선에는 총 n개의 팀이 참가했다. 팀은 1번부터 n번까지 번호가 매겨져 있다. 놀랍게도 올해 참가하는 팀은 작년에 참가했던 팀과 동일하다.

올해는 인터넷 예선 본부에서는 최종 순위를 발표하지 않기로 했다. 그 대신에 작년에 비해서 상대적인 순위가 바뀐 팀의 목록만 발표하려고 한다. (작년에는 순위를 발표했다) 예를 들어, 작년에 팀 13이 팀 6 보다 순위가 높았는데, 올해 팀 6이 팀 13보다 순위가 높다면, (6, 13)을 발표할 것이다.

창영이는 이 정보만을 가지고 올해 최종 순위를 만들어보려고 한다. 작년 순위와 상대적인 순위가 바뀐 모든 팀의 목록이 주어졌을 때, 올해 순위를 만드는 프로그램을 작성하시오. 하지만, 본부에서 발표한 정보를 가지고 확실한 올해 순위를 만들 수 없는 경우가 있을 수도 있고, 일관성이 없는 잘못된 정보일 수도 있다. 이 두 경우도 모두 찾아내야 한다.

입력

첫째 줄에는 테스트 케이스의 개수가 주어진다. 테스트 케이스는 100개를 넘지 않는다. 각 테스트 케이스는 다음과 같이 이루어져 있다.

  • 팀의 수 n을 포함하고 있는 한 줄. (2 ≤ n ≤ 500)
  • n개의 정수 ti를 포함하고 있는 한 줄. (1 ≤ ti ≤ n) ti는 작년에 i등을 한 팀의 번호이다. 1등이 가장 성적이 높은 팀이다. 모든 ti는 서로 다르다.
  • 상대적인 등수가 바뀐 쌍의 수 m (0 ≤ m ≤ 25000)
  • 두 정수 ai와 bi를 포함하고 있는 m줄. (1 ≤ ai < bi ≤ n) 상대적인 등수가 바뀐 두 팀이 주어진다. 같은 쌍이 여러 번 발표되는 경우는 없다.

출력

각 테스트 케이스에 대해서 다음을 출력한다.

  • n개의 정수를 한 줄에 출력한다. 출력하는 숫자는 올해 순위이며, 1등팀부터 순서대로 출력한다. 만약, 확실한 순위를 찾을 수 없다면 "?"를 출력한다. 데이터에 일관성이 없어서 순위를 정할 수 없는 경우에는 "IMPOSSIBLE"을 출력한다.

느낀점

  • 그래프 문제를 풀면서 어렵다고 느낀 부분은 말로 풀어진 문제를 그래프로 스스로 해석하여 풀어내는 사고방식이다.
  • 이 문제는 '정해진 우선순위에 맞게 전체 팀들의 순서를 나열해야 한다'는 점에서 '위상정렬'을 떠올릴 수 있어야 한다.
  • '각 팀'들을 '노드'로 작년 순위를 토대로 '팀 간 우선순위'를 방향성 간선으로 그린다.
  • 이후 제시해주는 바뀐 팀간 우선순위를 토대로 간선의 방향을 바꿔 그린다.
  • 이후 위상정렬을 통해 그래프를 그릴 수 있는지 확인해본다.
  • 위상정렬은 2가지 주의사항이 있다.
    1. 사이클이 발생하는 경우                                 => 일관성이 없다.
    2. 위상정렬의 경우의 수가 2가지 이상인 경우    => 확실한 순위를 찾을 수 없다.

 

원본문제 : https://www.acmicpc.net/problem/3665

 

3665번: 최종 순위

올해 ACM-ICPC 대전 인터넷 예선에는 총 n개의 팀이 참가했다. 팀은 1번부터 n번까지 번호가 매겨져 있다. 놀랍게도 올해 참가하는 팀은 작년에 참가했던 팀과 동일하다. 올해는 인터넷 예선 본부에

www.acmicpc.net

 

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

int t;
int n;
int m;
int indegree[501];
bool graph[501][501];

void topologySort() {
    queue<int> q;

    for (int i = 1; i <= n; i++) {
        if (indegree[i] == 0) {
            q.push(i);
        } 
    }

    vector<int> ans;
    bool not_certain = false;
    bool cycle = false;
    for (int i = 0; i < n; i++) {
        if (q.size() == 0) {
            cycle = true;
            break;
        }
        if (q.size() > 1) {
            not_certain = true;
            break;
        }

        int now = q.front();
        ans.push_back(now);
        q.pop();

        int chk_double = 0;
        for (int j = 1; j <= n; j++) {
            if (graph[now][j]) {
                indegree[j]--;
                if (indegree[j] == 0) {
                    q.push(j);
                }
            }
        }
    }

    if (cycle) {
        cout << "IMPOSSIBLE" << endl;
        return;
    }
    if (not_certain) {
        cout << "?" << endl;
        return;
    }
    
    for (int i = 0; i < n; i++) {
        cout << ans[i] << " ";    
    }
    cout << endl;

}

int main(void) {
    cin >> t;
    for (int i = 0; i < t; i++) {
        cin >> n;

        fill(indegree, indegree+501, 0);
        fill(graph[0], graph[0]+501*501, false);

        vector<int> v;
        for (int j = 0; j < n; j++) {
            int a;
            cin >> a;
            v.push_back(a);
        }

        for (int j = 0; j < n; j++) {
            for (int k = j+1; k < n; k++) {
                graph[v[j]][v[k]] = true;
                indegree[v[k]]++;
            }
        }

        cin >> m;
        for (int j = 0; j < m; j++) {
            int  a, b;
            cin >> a >> b;
            if (graph[a][b]) {
                indegree[a]++;
                indegree[b]--;
            }
            if (graph[b][a]) {
                indegree[a]--;
                indegree[b]++;
            }
            swap(graph[a][b], graph[b][a]);
        }

        topologySort();
    }
    return 0 ;
}

 

문제

공항에는 G개의 탑승구가 있으며, 각각의 탑승구는 1번부터 G번까지 번호로 구분된다. 공항에는 P개의 비행기가 차례대로 도착할 예정이며, i번째 비행기를 1번부터 gi(1 <= gi <= G) 탑승구 중 하나에 영구적으로 도킹하려 한다. 이 때, 다른 비행기가 도킹하지 않은 탑승구에만 도킹이 가능하다. 또한 P개의 비행기를 순서대로 도킹하다가 만약에 어떠한 탑승구에도 도킹할 수 없는 비행기가 나오는 경우, 그 시점에서 공항의 운행을 중단한다. 공항의 관리자는 최대한 많은 비행기를 공항에 도킹하고자 한다. 비행기를 최대 몇 대 도킹할 수 있는지를 출력해라

입력

  • 첫째 줄에는 탑승구의 수 G(1 <= G <= 100,000)가 주어진다.
  • 둘째 줄에는 비행기의 수 P(1 <= P <= 100,000)가 주어진다.
  • 다음 P개의 줄에는 각 비행기가 도킹할 수 있는 탑승구의 정보 g(1 <= g <= G)가 주어진다. 이는 i번째 비행기가 1번부터 gi번째(1 <= gi <= G) 탑승구 중 하나에 도킹할 수 있다는 의미이다.

출력

  • 첫째 줄에 도킹할 수 있는 비행기의 최대 개수를 출력해라.

느낀점

  • 이 문제는 처음에 단순 구현이 떠올랐다. 그래프 카테고리에 있음에도..이 문제는 추가적인 테스트케이스가 제공되지 않아 정답임을 확인할 수는 없었다. 아마 틀렸을 것이다. 그리고 답안을 보니 너무 쉽다.
  • 또한 union-find가 서로소를 판별하기 위한 알고리즘으로 대표적이지만 집합의 제한을 하기에도 사용됨을 깨닫게 해준 문제다.
  • 이 문제를 다시 푼다고 해도 union-find를 떠올릴지 의아하다...
  • '공항'을 root탑승구를 가진 하나의 '집합'으로 바라보고 하나의 비행기가 탑승구에 집합할 때마다 union 한다.
  • "더 이상 union할 수 있는 탑승구가 없다 == 0번 탑승구에 도킹해야 한다" 가 나오면 더이상 도킹할 수 없으므로  공항의 운행을 중단한다.
#include <iostream>
#include <vector>

using namespace std;

int g, p;
int parent[100001];

int findParent(int a) {
    if (parent[a] != a) {
        parent[a] = findParent(parent[a]);
    }
    return parent[a];
}

void unionParent(int a, int b) {
    a = findParent(a);
    b = findParent(b);

    if (a < b) {
        parent[b] = a;
    }
    else {
        parent[a] = b;
    }
}

int main(void) {
    cin >> g >> p;

    for (int i = 1; i <= g; i++) {
        parent[i] = i;
    }

    int ans = 0;
    for (int i = 0; i < p; i++) {
        int a;
        cin >> a;
        int root = findParent(a);
        if (root != 0) {
            ans++;
            unionParent(root, root-1);
        }
        else {
            break;
        }
    }

    cout << ans << endl;


    return 0 ;
}

Trie를 c++로 구현해봤다.

 

원문 : https://leetcode.com/problems/implement-trie-prefix-tree/

  • 트라이(Trie) : 문자열을 저장하고 효율적으로 탐색하기 위한 트리 형태의 자료구조이다.
  • 자식으로 a-z까지 TreeNode들과 문자의 끝을 표시하는 end flag를 갖는다.
class TrieNode {
private:
    bool endFlag;
    TrieNode* next[27 + 'a'];
public:
    TrieNode() {
        endFlag = false;
        memset(next, NULL, sizeof(next));
    }

    TrieNode* getNext(char c) {
        return next[c];
    }

    void setNext(char c) {
        next[c] = new TrieNode();
        // setEndFlag(false);
    }

    bool getEndFlag() {
        return endFlag;
    }

    void setEndFlag(bool endFlag) {
        this->endFlag = endFlag;
    }
};

class Trie {
private:
    TrieNode* root;
public:
    Trie() {
        root = new TrieNode();
    }

    void insert(string word) {
        TrieNode* node = root;
        for (char c : word) {
            if (node->getNext(c)) {
            }
            else {
                node->setNext(c);
            }
            node = node->getNext(c);
        }
        node->setEndFlag(true);
    }

    bool search(string word) {
        TrieNode* node = root;
        for (char c : word) {
            if (node->getNext(c)) {
                node = node->getNext(c);
            }
            else {
                return false;
            }
        }
        return node->getEndFlag();
    }

    bool startsWith(string prefix) {
        TrieNode* node = root;
        for (char c : prefix) {
            if (node->getNext(c)) {
                node = node->getNext(c);
            }
            else {
                return false;
            }
        }
        return true;       
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

 

처음으로 leetcode hard 문제를 풀었다.

BFS와 Memoization을 적용한 풀이다.

초반엔 구조체 만들어서 풀었지만, tuple과 tie라는 STL을 적용하니 훨씬 깔끔해졌다.

그리고 {}를 이용해 데이터를 묶어주면 tuple, vector형식으로 자동 변환해준다.

시간복잡도는 m * n * k, 즉 40*40*1600이다. 

class Solution {
public:
    int shortestPath(vector<vector<int>>& grid, int k) {
        vector<vector<vector<int>>> dp;       
        dp.assign(41, vector<vector<int>>(41, vector<int>(1601, 0)));
        
        int ms = grid.size(); 
        int ns = grid[0].size();
        int rb, cb, kb;
        int cnt = 0;
        
        queue<tuple<int, int, int> > q;
        q.push({0, 0, k});
        
        while (!q.empty()) {
            tie(rb, cb, kb) = q.front();
            q.pop();
            int nb = dp[rb][cb][kb];
            
            if (rb == ms-1 && cb == ns-1) {
                continue;
            }
            if (rb < ms-1) {
                if (grid[rb+1][cb] == 1 && kb > 0) {
                    if (!dp[rb+1][cb][kb-1]) {
                        dp[rb+1][cb][kb-1] = nb+1;                     
                        q.push({rb+1, cb, kb-1});
                    }
                }
                else if (grid[rb+1][cb] == 0) {
                    if (!dp[rb+1][cb][kb]) {
                        dp[rb+1][cb][kb] = nb+1;
                        q.push({rb+1, cb, kb});
                    }
                }
            }
            if (cb < ns-1) {
                if (grid[rb][cb+1] == 1 && kb > 0) {
                    if (!dp[rb][cb+1][kb-1]) {
                        dp[rb][cb+1][kb-1] = nb+1;
                        q.push({rb, cb+1, kb-1});                        
                    }
                }
                else if (grid[rb][cb+1] == 0) {
                    if (!dp[rb][cb+1][kb]) {
                        dp[rb][cb+1][kb] = nb+1;
                        q.push({rb, cb+1, kb});                        
                    }
                }
            }
            if (rb > 0) {
                if (cb == 0)
                    continue;
                if (grid[rb-1][cb] == 1 && kb > 0) {
                    if (!dp[rb-1][cb][kb-1]) {
                        dp[rb-1][cb][kb-1] = nb+1;
                        q.push({rb-1, cb, kb-1});                        
                    }
                }
                else if (grid[rb-1][cb] == 0) {
                    if (!dp[rb-1][cb][kb]) {
                        dp[rb-1][cb][kb] = nb+1;
                        q.push({rb-1, cb, kb});
                    }
                }
            }
            if (cb > 0) {
                if (rb == 0)
                    continue;
                if (grid[rb][cb-1] == 1 && kb > 0) {
                    if (!dp[rb][cb-1][kb-1]) {
                        dp[rb][cb-1][kb-1] = nb+1;
                        q.push({rb, cb-1, kb-1});                        
                    }
                }
                else if (grid[rb][cb-1] == 0) {
                    if (!dp[rb][cb-1][kb]) {
                        dp[rb][cb-1][kb] = nb+1;
                        q.push({rb, cb-1, kb});                        
                    }
                }
            }
        }
        
        int mini = 1601;
        for (int i = 0; i < 1601; i++) {
            if (dp[ms-1][ns-1][i] != 0)
                mini = min(mini, dp[ms-1][ns-1][i]);
        }
        
        if (ms == 1 && ns == 1)
            return 0;
        
        if (mini != 1601)
            return mini;
        else
            return -1;
    }
};

+ Recent posts