※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 1753번 문제인 최단경로이다.
문제는 아래 링크를 확인하자.

www.acmicpc.net/problem/1753

 

1753번: 최단경로

첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1≤V≤20,000, 1≤E≤300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1≤K≤V)가 주어진다.

www.acmicpc.net

이 문제는 다익스트라(Dijkstra) 알고리즘을 구현해보는 문제이다.

개념만 알고 직접 코딩해본 적이 없는 알고리즘이라 이번 기회에 자료를 찾아 구현해보았다.

 

먼저 각 노드까지의 거리의 배열을 준비한다. 이 때, 시작점을 제외한 모든 점까지의 거리를 도달할 수 없는 큰 수를 미리 넣어준다.

그 다음에는 남은 점중 시작점에서 가장 가까운 점을 찾아 그 점으로부터 다른 점까지의 거리를 그 다른 점까지의 기존 거리와 비교해 갱신한다.

남은 점중 가장 가까운 점을 찾는 것은 priority queue(우선순위 큐)를 이용해 구현할 수 있다. 남아있는 노드 중 시작점과 가장 가까운 노드를 priority queue에서 뽑아 쓰는 방식으로 구현할 수 있기 때문이다. 단, stl에서 제공하는 priority queue는 max heap이므로 최솟값을 받기 위해서는 거리에 -를 붙여서 넣어야 한다.

또한, priority queue에 원소를 넣는 시점 이후로 최단거리가 갱신된다면, priority queue에 갱신된 정보를 그대로 다시 넣고, 만약 priority queue에서 꺼낸 점이 이미 처리된 점이라면, 더 짧은 거리일 때의 데이터를 이미 기록했으므로 건너뛰어줘야 한다. 이를 위해 done 배열을 만들었다.

 

아래는 제출한 소스코드이다.

#include <iostream>
#include <vector>
#include <queue>
using std::vector;
using std::pair;
using std::cin;
using std::cout;
using std::priority_queue;

vector<pair<int, int>> graph[20001];
priority_queue<pair<int, int>> que;
int distance[20001];
bool done[20001];

int main()
{
    std::ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int V, E, K;
    cin >> V >> E >> K;
    for (int i = 1;i <= V;i++) {
        distance[i] = 1000000000;
    }
    distance[K] = 0;
    que.push({ 0,K });
    for (int i = 0;i < E;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back(pair<int, int> {v, w});
    }
    while (!que.empty()) { // Dijkstra
        int current = que.top().second; que.pop();
        if (done[current]) continue;
        done[current] = true;
        for (auto u : graph[current]) {
            int v = u.first, w = u.second;
            if (distance[current]+w<distance[v]) {
                distance[v] = distance[current] + w;
                que.push({ -distance[v],v });
            }
        }
    }
    for (int i = 1;i <= V;i++) {
        if (distance[i] == 1000000000) cout << "INF\n";
        else cout << distance[i] << "\n";
    }

    return 0;
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 1916 // C++] 최소비용 구하기  (0) 2021.02.13
[BOJ 1261 // C++] 알고스팟  (0) 2021.02.12
[BOJ 14502 // C++] 연구소  (0) 2021.02.10
[BOJ 2143 // C++] 두 배열의 합  (0) 2021.02.09
[BOJ 13398 // C++] 연속합 2  (0) 2021.02.08

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 14502번 문제인 연구소이다.
문제는 아래 링크를 확인하자.

www.acmicpc.net/problem/14502

 

14502번: 연구소

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다. 연구소는 크

www.acmicpc.net

문제에서 요구하는 조건인 "벽 3개 놓기"를 프로그래밍하는 것은 어려워보인다.

 

다행히 연구소의 가로 세로 범위가 작으므로, 이 문제에서는 벽 3개를 놓는 모든 경우의 수를 탐색해도 시간제한 내로 통과할 수 있을 것으로 보인다.

 

따라서 이 문제를 풀기 위해 고안한 알고리즘은 다음과 같다.

 

1) 문제에서 주어진 그래프를 읽어온다. 이 때, 빈 칸 좌표를 담은 벡터 blank와 바이러스 좌표를 담은 큐 originalque를 만든다.

 

2) 벡터 blank에서 세 원소를 고르는 순열(permutation)을 탐색한다. 이는 백트래킹을 이용하여 쉽게 구현할 수 있다. 여기서 고른 세 원소는 새로 벽을 세울 세 곳이다.

 

3) 2에서 구한 세 곳에 벽을 추가한 연구소에서 originalque를 복사한 que를 시작점으로 bfs를 진행한다. 그 뒤, 연구소 범위 안에 남아있는 0의 개수를 세어 기존 답 후보와 비교한다.

 

아래는 제출한 소스코드이다.

#include <iostream>
#include <vector>
#include <queue>
using std::cin;
using std::cout;
using std::queue;
using std::vector;

int ans = 0;
int row, column;

struct point {
    int row, column;
};

int originallab[8][8];
int lab[8][8];
bool labvisited[8][8];
queue<point> originalque;
queue<point> que;

int dr[4] = { 1,-1,0,0 };
int dc[4] = { 0,0,1,-1 };

void bfs(vector<point> wall) { // 3개의 세울 벽 좌표를 담고 있는 벡터 wall

    for (int r = 0;r < row;r++) { // lab과 labvisited 초기화 (입력에서 주어진 것과 동일)
        for (int c = 0;c < column;c++) {
            lab[r][c] = originallab[r][c];
            labvisited[r][c] = false;
        }
    }

    que = originalque; // que 초기화 (입력에서 주어진 것과 동일)
    
    vector<point>::iterator iter = wall.begin(); // 벡터 wall에 있는 좌표에 벽을 세운다
    for (iter;iter != wall.end();iter++) {
        point p = *iter;
        lab[p.row][p.column] = 1;
    }
    
    while (!que.empty()) { // BFS로 바이러스를 퍼뜨린다
        point current = que.front(); que.pop();
        int r = current.row;
        int c = current.column;
        for (int i = 0;i < 4;i++) {
            int rr = r + dr[i];
            int cc = c + dc[i];
            if (0 <= rr and rr < row and 0 <= cc and cc < column) {
                if ((!labvisited[rr][cc]) and (lab[rr][cc] == 0)) {
                    labvisited[rr][cc] = true;
                    lab[rr][cc] = 2;
                    point p; p.row = rr; p.column = cc;
                    que.push(p);
                }
            }
        }
    }

    int cnt = 0; // 연구소에 있는 0의 개수를 센다
    for (int r = 0;r < row;r++) {
        for (int c = 0;c < column;c++) {
            if (lab[r][c] == 0) cnt++;
        }
    }

    if (cnt > ans) ans = cnt;
}

vector<point> blank;
vector<point> chosenwall;
void backtracking(int N) {
    if (chosenwall.size() == 3) {//고른 좌표가 3개이면 이 경우에 대해 조사
        bfs(chosenwall);
    }
    else {//고른 좌표가 3개가 아니면 좌표를 추가
        vector<point>::iterator iter = blank.begin();
        for (int i = 0;i < N;i++) {
            iter++;
        }
        for (iter;iter != blank.end();iter++) {
            chosenwall.push_back(*iter);
            backtracking(N + 1);
            chosenwall.pop_back();
            N++;
        }
    }
}

int main()
{
    cin >> row >> column;
    for (int r = 0;r < row;r++) {
        for (int c = 0;c < column;c++) {
            int x; cin >> x;
            originallab[r][c] = x;
            if (x == 0) {
                point p; p.row = r; p.column = c;
                blank.push_back(p);
            }
            if (x == 2) {
                point p; p.row = r; p.column = c;
                originalque.push(p);
            }
        }
    }

    backtracking(0);

    cout << ans;

    return 0;
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 1261 // C++] 알고스팟  (0) 2021.02.12
[BOJ 1753 // C++] 최단경로  (0) 2021.02.11
[BOJ 2143 // C++] 두 배열의 합  (0) 2021.02.09
[BOJ 13398 // C++] 연속합 2  (0) 2021.02.08
[BOJ 14686 // C++] Sum Game  (0) 2021.02.07

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 2143번 문제인 두 배열의 합이다.
문제는 아래 링크를 확인하자.

www.acmicpc.net/problem/2143

 

2143번: 두 배열의 합

첫째 줄에 T(-1,000,000,000 ≤ T ≤ 1,000,000,000)가 주어진다. 다음 줄에는 n(1 ≤ n ≤ 1,000)이 주어지고, 그 다음 줄에 n개의 정수로 A[1], …, A[n]이 주어진다. 다음 줄에는 m(1≤m≤1,000)이 주어지고, 그 다

www.acmicpc.net

각 배열 A, B의 크기는 최대 1000으로 그리 큰 숫자가 아니라는 것에 주목하자.

이 문제에 사용되는 연산은 대부분 간단한 덧셈의 반복이므로, 모든 A의 구간합과 B의 구간합을 구해 같은 쌍을 찾아주어도 무리가 없다.

 

1~n, 2~n, 3~n, ... (n-1)~n, n~n의 범위의 합을 각각 저장해두면, index n+1 이하 범위에서 나올 수 있는 구간합은 n+1번째 원소 자신과, 구해진 앞쪽 범위의 n을 포함하는 구간합에 n+1번째 원소를 각각 더한 것들으로 전부 구할 수 있다. 이를 이용하여 구간합의 배열을 각각 만든다. 이 때, B의 구간합을 T에서 뺀 값을 저장한다면, 이 문제는 같은 값들의 쌍을 구하는 문제로 간단히 바뀐다.

 

그 다음으로, 두 배열을 정렬한다. 마지막으로 각 배열의 index를 가리키는 두 포인터 ptA, ptB를 만들어, index 0에서부터 두 배열을 다음과 같이 탐색한다.

 

1) ptA가 가리키는 A쪽 구간합배열값이 ptB가 가리키는 B쪽 구간합배열값보다 작다면 ptA++, 크다면 ptB++

2) 둘이 같다면, A쪽 구간합배열에 그 수가 안 나올 때까지 ptA++을 하며 A쪽 구간합배열에 그 수가 들어있는 개수 cntA를 구한다. B쪽도 마찬가지로 ptB++를 하며 B쪽 구간합배열에 그 수가 들어있는 개수 cntB를 구한다. 출력할 답 변수에 cntA * cntB를 더해준다. [cntA * cntB의 값이 int 범위를 넘어갈 수 있음에 유의하자.]

3) ptA 또는 ptB가 각 배열의 범위를 넘어설 수 있다는 것을 유의하며 위 과정을 반복한다.

 

아래는 제출한 소스코드이다.

#include <iostream>
#include <algorithm>
using std::cin;
using std::cout;
using std::sort;

int A[1000];
int B[1000];
int prefixsumA[1000000];
int prefixsumB[1000000];

int main()
{
    std::ios::sync_with_stdio(0);
    cin.tie(0);

    int T;
    cin >> T;
    int N;
    cin >> N;
    int index1 = 0;
    for (int i = 0;i < N;i++) { // A의 구간합 배열 만들기
        int x;
        cin >> x;
        for (int j = 0;j <= i;j++) {
            A[j] += x;
            prefixsumA[index1] = A[j];
            index1++;
        }
    }
    int index2 = 0;
    cin >> N;
    for (int i = 0;i < N;i++) { // B의 (T - 구간합) 배열 만들기
        int x;
        cin >> x;
        for (int j = 0;j <= i;j++) {
            B[j] += x;
            prefixsumB[index2] = T - B[j];
            index2++;
        }
    }
    sort(prefixsumA, prefixsumA + index1); // 정렬
    sort(prefixsumB, prefixsumB + index2);
    int ptA = 0; // Two Pointer
    int ptB = 0;
    long long ans = 0;
    while (ptA < index1 and ptB < index2) {
        int AA = prefixsumA[ptA];
        int BB = prefixsumB[ptB];
        if (AA < BB) ptA++;
        else if (AA > BB) ptB++;
        else {
            long long cntA = 0;
            long long cntB = 0;
            while (prefixsumA[ptA] == AA) {
                ptA++;
                cntA++;
                if (ptA >= index1) break;
            }
            while (prefixsumB[ptB] == BB) {
                ptB++;
                cntB++;
                if (ptB >= index2) break;
            }
            ans += cntA * cntB;
        }
    }

    cout << ans;

    return 0;
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 1753 // C++] 최단경로  (0) 2021.02.11
[BOJ 14502 // C++] 연구소  (0) 2021.02.10
[BOJ 13398 // C++] 연속합 2  (0) 2021.02.08
[BOJ 14686 // C++] Sum Game  (0) 2021.02.07
[BOJ 1107 // C++] 리모컨  (0) 2021.02.06

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 13398번 문제인 연속합 2이다.
문제는 아래 링크를 확인하자.

www.acmicpc.net/problem/13398

 

13398번: 연속합 2

첫째 줄에 정수 n(1 ≤ n ≤ 100,000)이 주어지고 둘째 줄에는 n개의 정수로 이루어진 수열이 주어진다. 수는 -1,000보다 크거나 같고, 1,000보다 작거나 같은 정수이다.

www.acmicpc.net

연속한 수(단, 중간에 한번 딱 한칸 건너뛸 수 있다)들의 합 가운데 최댓값이 무엇인지 구하는 문제이다.

글쓴이는 Dynamic Programming으로 다음과 같이 풀어보았다.

 

N이 4 이상이라고 가정하자.

N-1개의 수까지 보았을 때 다음을 알고 있다고 생각하자.

 

1) (한번도 건너뛰지 않으면서 N-1번째 수를 포함하는 구간합) 중 구간합이 최대인 경우의 구간합 jump0

2) (한번 건너뛴 상태로 N-1번째 수를 포함하는 구간합) 중 구간합이 최대인 경우의 구간합 jump1

3) (한번도 건너뛰지 않으면서 N-2번째 수를 포함하는 구간합) 중 구간합이 최대인 경우의 구간합 oldjump0

4) 출력할 답을 저장한 ans

 

이 때, N번째 수 current를 추가하면, 각 변수는 다음과 같이 계산할 수 있다.


jump0 = max(jump0+current, current)
jump1 = max(jump1, oldjump0) + current

oldjump0 = jump0;

ans = max(ans, max(jump0, jump1))

 

N이 4 이상인 이유는 jump1이 N이 3 이하일 경우 제대로 정의가 안 되기 때문이다.

 

아래는 제출한 소스코드이다.

#include <iostream>
using std::cin;
using std::cout;
using std::max;
int main()
{
    std::ios::sync_with_stdio(0);
    cin.tie(0);

    int N;
    cin >> N;
    if (N == 1) { // 예외 N = 1
        int x;
        cin >> x;
        cout << x;
    }
    else if (N == 2) { // 예외 N = 2
        int x, y;
        cin >> x >> y;
        cout << max(max(x, y), x + y);
    }
    else { // Base case N = 3
        int x, y, z;
        cin >> x >> y >> z;
        int oldjump0, jump0, jump1;
        oldjump0 = max(y, x + y);
        jump0 = max(max(z, y + z), x + y + z);
        jump1 = x + z;
        int ans = max(max(jump0, jump1), max(max(x, y), x + y));
        for (int i = 3; i < N;i++) { // N = 4 이상은 Dynamic Programming으로
            int current;
            cin >> current;
            int oldjump0temp = jump0;
            int jump0temp = max(jump0+current, current);
            int jump1temp = max(jump1, oldjump0) + current;
            oldjump0 = jump0;
            jump0 = jump0temp;
            jump1 = jump1temp;
            ans = max(ans, max(jump0, jump1));
        }
        cout << ans;
    }

    return 0;
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 14502 // C++] 연구소  (0) 2021.02.10
[BOJ 2143 // C++] 두 배열의 합  (0) 2021.02.09
[BOJ 14686 // C++] Sum Game  (0) 2021.02.07
[BOJ 1107 // C++] 리모컨  (0) 2021.02.06
[BOJ 7662 // C++] 이중 우선순위 큐  (0) 2021.02.05

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 14686번 문제인 Sum Game이다.
문제는 아래 링크를 확인하자.

www.acmicpc.net/problem/14686

 

14686번: Sum Game

The first line of input will contain an integer N (1 ≤ N ≤ 100 000). The second line will contain N space-separated non-negative integers representing the number of runs scored by the Swifts on each day, in order. The third line will contain N space-se

www.acmicpc.net

이 문제는 Prefix Sum에 입문하기 좋은 문제이다.

 

글쓴이는 이 문제를 다음과 같이 해결했다.

 

1) 크기 10만의 배열을 준비하고 Swifts 팀의 경기 점수의 prefix sum을 배열에 채워넣는다.

2) prefix sum을 계산할 정수를 선언하고, 들어오는 Semaphores 팀의 경기점수를 더해가면서 미리 준비한 배열과 값이 같은지 비교해나간다.

 

아래는 제출한 소스코드이다.

#include <iostream>
using std::cin;
using std::cout;
int games[100000];

int main()
{
    std::ios::sync_with_stdio(0);
    cin.tie(0);

    int N;
    cin >> N;
    for (int i = 0;i < N;i++) { // Swifts 팀의 prefix sum array 만들기
        int x;
        cin >> x;
        if (i == 0) games[i] = x;
        else games[i] = games[i - 1] + x;
    }
    int prefixsum = 0; // Semaphores 팀의 prefix sum
    int ans = 0;
    for (int i = 0;i < N;i++) {
        int x;
        cin >> x;
        prefixsum += x;
        if (prefixsum == games[i]) ans = i + 1; // 두 팀의 prefixsum이 같을 때 i+1을 반환, 없다면 0이 유지됨
    }
    cout << ans;

    return 0;
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 2143 // C++] 두 배열의 합  (0) 2021.02.09
[BOJ 13398 // C++] 연속합 2  (0) 2021.02.08
[BOJ 1107 // C++] 리모컨  (0) 2021.02.06
[BOJ 7662 // C++] 이중 우선순위 큐  (0) 2021.02.05
[BOJ 1780 // C++] 종이의 개수  (0) 2021.02.04

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 1107번 문제인 리모컨이다.
문제는 아래 링크를 확인하자.

www.acmicpc.net/problem/1107

 

1107번: 리모컨

첫째 줄에 수빈이가 이동하려고 하는 채널 N (0 ≤ N ≤ 500,000)이 주어진다.  둘째 줄에는 고장난 버튼의 개수 M (0 ≤ M ≤ 10)이 주어진다. 고장난 버튼이 있는 경우에는 셋째 줄에는 고장난 버튼

www.acmicpc.net

처음에는 이 문제를 시도할 때 다음과 같은 알고리즘을 구상했었다.

 

1) 만들려는 수까지 +나 -만을 이용하여 필요한 버튼을 누르는 횟수를 구해 저장한다.

2) 만들려는 수보다 크거나 같은 수 중 숫자버튼으로 누를 수 있는 가장 작은 수를 구한다. 구한 수를 입력하는 횟수와 구한 수에서 만들려는 수까지 - 버튼을 눌러야하는 수를 합해 저장한다.

3) 만들려는 수보다 작거나 같은 수 중 숫자버튼으로 누를 수 있는 가장 큰 수를 구한다. 구한 수를 입력하는 횟수와 구한 수에서 만들려는 수까지 +버튼을 눌러야하는 수를 합해 저장한다.

4) 1, 2, 3에서 구한 수중 최솟값을 출력한다.

 

2-1) 큰 자릿수부터 숫자를 읽어내려온다. 누를 수 없는 자릿수를 만나면, 그 자릿수에 1씩 더해 이 수보다 큰 자릿수의 숫자들이 다 누를 수 있는 수가 되게끔 만든다. 아랫자리수들을 전부 누를 수 있는 가장 작은 숫자로 바꾼다.

2-2) 큰 자릿수부터 숫자를 읽어내려온다. 누를 수 없는 자릿수를 만나면, 그 자릿수에서 1씩 빼서 이 수보다 큰 자릿수의 숫자들이 다 누를 수 있는 수가 되게끔 만든다. 아랫자리수들을 전부 누를 수 있는 가장 큰 숫자로 바꾼다.

 

 

그러나, 이 문제의 숫자의 범위가 50만 이하여서 이런 구체적인 알고리즘이 필요할 것 같지 않았고, 처음에 누를 채널 숫자 후보로 가능한 모든 숫자를 탐색하는 방식으로 방향을 틀었다.

 

제출한 알고리즘은 다음과 같다.

 

1) 만들려는 수까지 +나 -만을 이용하여 필요한 버튼을 누르는 횟수를 구해 저장한다.

2) 0부터 100만까지 숫자 가운데 누를 수 있는 숫자들을 구해 (숫자의 자릿수) + (숫자-만들려는숫자) 들의 최솟값을 구한다.

3) 1과 2의 최솟값을 출력한다.

 

아래는 제출한 소스코드이다.

#include <iostream>
using std::cin;
using std::cout;
using std::min;

bool usable[10] = { true, true, true, true, true, true, true, true, true, true };

int press(int n, int i) {
    int cnt = abs(n-i);
    if (i == 0) { // 예외처리 - 0의 경우
        if (!usable[0]) return 1000000;
        cnt++;
    }
    while (i > 0) { // 0의 경우 반복문을 들어올 수 없으므로 예외처리
        int x = i % 10;
        if (!usable[x]) { // 누를 수 없는 자리가 포함되어있다면
            return 1000000; // 최솟값이 될 수 없는 불가능한 값을 리턴
        }
        cnt++; // 자릿수마다 버튼 누르는 횟수가 1씩 증가
        i /= 10;
    }
    return cnt;
}

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 0;i < m;i++) {
        int temp;
        cin >> temp;
        usable[temp] = false;;
    }
    int ans = abs(n - 100);
    for (int i = 0;i < 1000000;i++) { // 모든 가능한 숫자 시도
        ans = min(ans, press(n,i));
    }

    cout << ans;

    return 0;
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 13398 // C++] 연속합 2  (0) 2021.02.08
[BOJ 14686 // C++] Sum Game  (0) 2021.02.07
[BOJ 7662 // C++] 이중 우선순위 큐  (0) 2021.02.05
[BOJ 1780 // C++] 종이의 개수  (0) 2021.02.04
[BOJ 7569 // C++] 토마토  (0) 2021.02.03

+ Recent posts