문제 출처:https://www.acmicpc.net/problem/1325



 문제


해커 김지민은 잘 알려진 어느 회사를 해킹하려고 한다. 이 회사는 N개의 컴퓨터로 이루어져 있다. 김지민은 귀찮기 때문에, 한 번의 해킹으로 여러 개의 컴퓨터를 해킹 할 수 있는 컴퓨터를 해킹하려고 한다.

이 회사의 컴퓨터는 신뢰하는 관계와, 신뢰하지 않는 관계로 이루어져 있는데, A가 B를 신뢰하는 경우에는 B를 해킹하면, A도 해킹할 수 있다는 소리다.

이 회사의 컴퓨터의 신뢰하는 관계가 주어졌을 때, 한 번에 가장 많은 컴퓨터를 해킹할 수 있는 컴퓨터의 번호를 출력하는 프로그램을 작성하시오.


 입력


첫째 줄에, N과 M이 들어온다. N은 10,000보다 작거나 같은 자연수, M은 100,000보다 작거나 같은 자연수이다. 둘째 줄부터 M개의 줄에 신뢰하는 관계가 A B와 같은 형식으로 들어오며, "A가 B를 신뢰한다"를 의미한다. 컴퓨터는 1번부터 N번까지 번호가 하나씩 매겨져 있다.


 출력


첫째 줄에, 김지민이 한 번에 가장 많은 컴퓨터를 해킹할 수 있는 컴퓨터의 번호를 오름차순으로 출력한다.





위 그림은 출처에 있는 예제 입력을 그림으로 나타낸것인데 여기서 주의 하셔야 하는점은 그래프에 방향이 있는 방향 그래프 


란 것이고 입력에 대한 신뢰 관계를 이런식으로 표현해주어야 DFS, BFS연산을 수월하게 수행 할 수 있습니다.


위 그림에 대한 정답은 1,2번 노드가 각각 총 4개의 노드를 해킹 할 수 있기 때문에 1,2번 노드가 정답이 됩니다.




이제 문제를 풀어보자면 일단 그래프를 표현하는 것 부터 시작인데 그래프를 인접 행렬 그래프로 표현 한다면 문제의 n 값이 


최대 10,000이므로 2차원 배열 graph[10,000][10,000] 을 만들면 메모리 초과 오류가 나게됩니다. 따라서 그래프는 인접 리


리스트 그래프를 사용해야 됩니다.


인접 리스트로 그래프를 만들었다면 이제 모든 노드에 대해서 DFS, BFS연산을 수행하고 가장 많이 해킹 할 수 있는 노드를 찾


아서 오름차순으로 출력해주면 됩니다. 




저의 경우는 처음에 재귀를 이용한 DFS를 사용한다면 시간초과가 뜰거라 생각하고 큐를 이용한 BFS방법으로 문제를 풀었는


데 무엇이 잘못된건지 계속 시간초과를 받아서 결국 재귀를 이용한 DFS로 문제를 풀었습니다.

 


 

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
56
57
58
59
60
61
62
63
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cmath>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <ctime>
#include <string.h>
using namespace std;
 
vector<int> lstList[10001];
int maxCount = -1;
int visited[10001];
int cnt;
 
void DFS(int node) {
    visited[node] = true;
 
    for (int i = 0; i < lstList[node].size();i++) {
        int next = lstList[node][i];
        if (!visited[next]) {
            cnt++;
            DFS(next);
        }
    }
}
 
int main(void)
{
    int result[10001];
    int n, m;
    memset(result, -1sizeof(result));
    scanf("%d %d"&n, &m);
 
    for (int i = 0; i < m; i++) {
        int a, b;
        scanf("%d %d"&a, &b);
        lstList[b].push_back(a);
    }
    
    for (int i = 0; i < n + 1; i++) {
        int temp;
        if (lstList[i].size()!=0) {
            memset(visited, falsesizeof(visited));
            cnt = 0;
            DFS(i);
            if (maxCount < cnt)
                maxCount = cnt;
            result[i] = cnt;
        }
    }
    
    for (int i = 0; i < n + 1;i++) {
        if (result[i] == maxCount)
            printf("%d ", i);
    }
 
    return 0;
}
cs

'알고리즘 문제 > DFS,BFS' 카테고리의 다른 글

[백준] 16234번: 인구 이동 - C++  (0) 2019.06.01
[백준] 10026번: 적록색약 - C++  (0) 2019.01.22

문제 출처:https://www.acmicpc.net/problem/10816



 문제


숫자 카드는 정수 하나가 적혀져 있는 카드이다. 상근이는 숫자 카드 N개를 가지고 있다. 정수 M개가 주어졌을 때, 이 수가 적혀있는 숫자 카드를 상근이가 몇 개 가지고 있는지 구하는 프로그램을 작성하시오.


 입력


첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이가 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.

셋째 줄에는 M(1 ≤ M ≤ 500,000)이 주어진다. 넷째 줄에는 상근이가 몇 개 가지고 있는 숫자 카드인지 구해야 할 M개의 정수가 주어지며, 이 수는 공백으로 구분되어져 있다. 이수도 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.


 출력


첫째 줄에 입력으로 주어진 M개의 수에 대해서, 각 수가 적힌 숫자 카드를 상근이가 몇 개 가지고 있는지를 공백으로 구분해 출력한다.


이 문제는 이진탐색 알고리즘을 약간 수정한 Lower bound, Upper bound 알고리즘을 써야 수월하게 풀이가 가능합니다.


Lower bound 란 찾고자 하는 값 이상이 처음 나타나는 위치 입니다.  

예를들어 정렬된 데이터 {1, 3, 4, 6, 6, 7, 8, 10} 에서 6이상인 값이 처음 나오는 위치는 3 입니다.


Lower bound 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
int lower_binary(int*arr,int target,int size) {
    int mid, start, end;
    start = 0end = size - 1;
 
    while (end > start){
        mid = (start + end/ 2;
        if (arr[mid] >= target)
            end = mid;
        else start = mid + 1;
    }
    return end;
}
cs


Upper bound 란 찾고자 하는 값보다 큰 값이 처음으로 나타나는 위치 입니다. 

예를들어 정렬된 데이터 {1, 3, 4, 6, 6, 7, 8, 10} 에서 6보다 큰 값이 처음 나오는 위치는 5 입니다.


Upper bound 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
int upper_binary(int*arr, int target,int size) {
    int mid, start, end;
    start = 0end = size - 1;
 
    while (end > start) {
        mid = (start + end/ 2;
        if (arr[mid] > target)
            end = mid;
        else start = mid + 1;
    }
    return end;
}
cs


위 글만 보고 눈치 채신분도 있겠지만 이 문제는 정렬된 숫자카드에서 상근이가 구해야 할 정수n 을 

Upper bound(n) - Lower bound(n) 을 하게되면 상근이가 숫자 카드를 몇개 가지고 있는지 알 수 있습니다. 


출처에 나와있는 예제로 설명해보면 


{6, 3, 2, 10, 10, 10, -10, 7, 3}의 숫자카드가 있습니다. 일단 Lower, Upper bound알고리즘은 기본적으로 이진탐색에서 응용하는 알고리즘이므로 이진탐색과 마찬가지로 배열이 정렬되어야 합니다. 


따라서 배열을 {-10, 2, 3, 3, 6, 7, 10, 10, 10}으로 정렬을 해줍니다. 이 배열 안에 숫자 10이 적힌 카드가 몇개인지 알고싶다면 

Upper bound(10) - Lower bound(10)을 하게 되면 Upper bound(10)은 9를 반환하고 Lower bound(10)은 6을 반환하기 때문에 9 - 6 = 3  이라는 결과를 쉽게 구할 수 있습니다.


단 한가지 주의해야 할 점은 배열{3} 에서 3의 갯수를 알고싶다고 했을때 위의 알고리즘을 쓰게되면 1이 아니라 0이 나오는 오류가 있습니다. 따라서 이 부분에 대하여 예외처리를 해주어야 합니다.


전체 코드입니다.


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
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cmath>
 
using namespace std;
 
int lower_binary(int*arr,int target,int size) {
    int mid, start, end;
    start = 0end = size - 1;
 
    while (end > start){
        mid = (start + end/ 2;
        if (arr[mid] >= target)
            end = mid;
        else start = mid + 1;
    }
    return end;
}
 
int upper_binary(int*arr, int target,int size) {
    int mid, start, end;
    start = 0end = size - 1;
 
    while (end > start) {
        mid = (start + end/ 2;
        if (arr[mid] > target)
            end = mid;
        else start = mid + 1;
    }
    return end;
}
 
int main(void)
{
    int n, m, temp, target,lower,upper;
 
    scanf("%d"&n);
    int *m_arr = new int[n];
    for (int i = 0; i < n; i++) {
        scanf("%d"&m_arr[i]);
    }
    sort(m_arr, m_arr + n);
    
    scanf("%d"&m);
    int *arr = new int[m];
    int *result = new int[m];
    for (int i = 0; i < m; i++) {
        scanf("%d"&arr[i]);
        result[i] = 0;
    }
 
    for (int i = 0; i < m; i++) {
        lower = lower_binary(m_arr, arr[i], n);
        upper = upper_binary(m_arr, arr[i], n);
        if (upper == n - 1 && m_arr[n - 1== arr[i])
            upper++;
        result[i] = upper - lower;
    }
 
    for (int i = 0; i < m; i++) {
        printf("%d ", result[i]);
    }
    return 0;
}
cs



'알고리즘 문제 > 이진 탐색' 카테고리의 다른 글

[백준] 2805번: 나무 자르기 - C++  (0) 2019.04.21

문제 출처:https://www.acmicpc.net/problem/2805


 

 문제

 

상근이는 나무 M미터가 필요하다. 근처에 나무를 구입할 곳이 모두 망해버렸기 때문에, 정부에 벌목 허가를 요청했다. 정부는 상근이네 집 근처의 나무 한 줄에 대한 벌목 허가를 내주었고, 상근이는 새로 구입한 목재절단기을 이용해서 나무를 구할것이다.

목재절단기는 다음과 같이 동작한다. 먼저, 상근이는 절단기에 높이 H를 지정해야 한다. 높이를 지정하면 톱날이 땅으로부터 H미터 위로 올라간다. 그 다음, 한 줄에 연속해있는 나무를 모두 절단해버린다. 따라서, 높이가 H보다 큰 나무는 H 위의 부분이 잘릴 것이고, 낮은 나무는 잘리지 않을 것이다. 예를 들어, 한 줄에 연속해있는 나무의 높이가 20, 15, 10, 17이라고 하자. 상근이가 높이를 15로 지정했다면, 나무를 자른 뒤의 높이는 15, 15, 10, 15가 될 것이고, 상근이는 길이가 5인 나무와 2인 나무를 들고 집에 갈 것이다. (총 7미터를 집에 들고 간다)

상근이는 환경에 매우 관심이 많기 때문에, 나무를 필요한 만큼만 집으로 가져가려고 한다. 이때, 적어도 M미터의 나무를 집에 가져가기 위해서 절단기에 설정할 수 있는 높이의 최댓값을 구하는 프로그램을 작성하시오.

 

 입력

 

첫째 줄에 나무의 수 N과 상근이가 집으로 가져가려고 하는 나무의 길이 M이 주어진다. (1 ≤ N ≤ 1,000,000, 1 ≤ M ≤ 2,000,000,000)

둘째 줄에는 나무의 높이가 주어진다. 나무의 높이의 합은 항상 M을 넘기 때문에, 상근이는 집에 필요한 나무를 항상 가

져갈 수 있다. 높이는 1,000,000,000보다 작거나 같은 양의 정수 또는 0이다.

 

 출력

 

적어도 M미터의 나무를 집에 가져가기 위해서 절단기에 설정할 수 있는 높이의 최댓값을 출력한다.

 

이진 탐색을 이용하는 문제지만 이 문제에선 배열에 대해 탐색을 진행하는것이 아니라 절단기의 높이에 대해서 이진탐색 알고리즘을 사용하기 때문에 배열의 정렬은 필요 없습니다.

 

1. 절단기의 높이를 최고로 큰 나무의 높이로 설정한다.

 

2. 절단기의 높이에 대해 이진탐색 알고리즘을 수행한다.

 

3. 이진 탐색 알고리즘 도중 절단기의 수치가 달라지면 나무를 잘라 상근이가 필요로 하는 수치인지 확인한다.

 

전체적인 문제의 흐름은 이런식인데 추가적으로 몇가지 확인해줘야 할 사항이 있습니다.

 

 

첫번째로 이진 탐색을 이용하여 절단기로 나무를 다 잘라봐도 상근이가 원하는 나무의 길이와 딱 맞아떨어지지 않을수도 있습니다.

 

예를들어 나무의 수 N:2 상근이가 가져가려고 하는 나무의 길이 M: 11

나무의 길이:10, 10

이런식으로 문제가 주어지면 절단기의 길이가 5일땐 나무의 길이가 10으로 상근이가 원하는 길이보다 짧고 절단기의 길이가 4일땐 나무의 길이가 12로 상근이가 원하는 것보다 많아집니다. 문제에선 적어도 M미터의 나무를 집에 가져_가기 위해서 절단기에 설정할 수 있는 높이의 최댓값 이라고명시가 되어 있기 때문에 이 경우에선 절단기의 높이를 4로 맞춰주는게 정답입니다. 따라서 이진 탐색 알고리즘을 쓸때 이에대한 예외처리를 해주어야 합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int binary(int low, int high, int *arr, int m, int n) {
    int mid = (low + high) / 2;
    long long int result = tree(arr, mid, n, m);
 
    if (result == m)
        return mid;
    else if (low>=high) {
        if (result >= m)
            return high;
        else
            return high - 1;
    }
    else if (result > m)
        binary(mid + 1, high, arr, m, n);
    else
        binary(low, mid - 1, arr, m, n);
}
cs

 

두번째로 나무의 최대 길이가 2,000,000,000 이므로 절단기로 나무를 자르며 길이를 더해줄때 변수를 int형으로 선언했다면 오버플로우가 걸릴수 있다는 것입니다. 이 경우는 long long int를 쓰는것으로 해결됩니다.

 

1
2
3
4
5
6
7
long long int tree(int *arr, int height,int n, int m) {
    long long int count = 0;
    for (int i = 0; i < n; i++) {
        count += (arr[i] - height) > 0 ? arr[i] - height : 0;
    }
    return count;
}
cs

 

전체 코드입니다.

 

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
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cmath>
 
long long int tree(int *arr, int height,int n, int m) {
    long long int count = 0;
    for (int i = 0; i < n; i++) {
        count += (arr[i] - height) > 0 ? arr[i] - height : 0;
    }
    return count;
}
 
int binary(int low, int high, int *arr, int m, int n) {
    int mid = (low + high) / 2;
    long long int result = tree(arr, mid, n, m);
 
    if (result == m)
        return mid;
    else if (low>=high) {
        if (result >= m)
            return high;
        else
            return high - 1;
    }
    else if (result > m)
        binary(mid + 1, high, arr, m, n);
    else
        binary(low, mid - 1, arr, m, n);
}
int main(void)
{
    int n, m, height = -1;
    scanf("%d %d"&n, &m);
    int *arr = new int[n];
    for (int i = 0; i < n; i++) {
        scanf("%d"&arr[i]);
        if (arr[i] > height)
            height = arr[i];
    }
 
    printf("%d", binary(0, height, arr, m, n));
 
    return 0;
}
cs
 

'알고리즘 문제 > 이진 탐색' 카테고리의 다른 글

[백준] 10816번: 숫자 카드 2 - C++  (1) 2019.04.26

문제 출처:https://www.acmicpc.net/problem/10026



 문제


적록색약은 빨간색과 초록색의 차이를 거의 느끼지 못한다. 따라서, 적록색약인 사람이 보는 그림은 아닌 사람이 보는 그림과는 좀 다를 수 있다.

크기가 N×N인 그리드의 각 칸에 R(빨강), G(초록), B(파랑) 중 하나를 색칠한 그림이 있다. 그림은 몇 개의 구역으로 나뉘어져 있는데, 구역은 같은 색으로 이루어져 있다. 또, 같은 색상이 상하좌우로 인접해 있는 경우에 두 글자는 같은 구역에 속한다. (색상의 차이를 거의 느끼지 못하는 경우도 같은 색상이라 한다)

예를 들어, 그림이 아래와 같은 경우에

RRRBB
GGBBB
BBBRR
BBRRR
RRRRR

적록색약이 아닌 사람이 봤을 때 구역의 수는 총 4개이다. (빨강 2, 파랑 1, 초록 1) 하지만, 적록색약인 사람은 구역을 3개 볼 수 있다. (빨강-초록 2, 파랑 1)

그림이 입력으로 주어졌을 때, 적록색약인 사람이 봤을 때와 아닌 사람이 봤을 때 구역의 수를 구하는 프로그램을 작성하시오.


 입력


첫째 줄에 N이 주어진다. (1 ≤ N ≤ 100)

둘째 줄부터 N개 줄에는 그림이 주어진다.


 출력


적록색약이 아닌 사람이 봤을 때의 구역의 개수와 적록색약인 사람이 봤을 때의 구역의 수를 공백으로 구분해 출력한다.


DFS, BFS 알고리즘을 이용하는 기초적인 문제입니다.

배열에서 R, G, B 각각의 영역 갯수를 구하는 문제인데 제약조건이 하나 붙습니다.

R, G, B에 대한 영역 (R, G), B에 대한 영역 갯수 2가지를 구하여야 합니다.

저는 처음부터 2차원 배열을 2개 만들어서 입력받을때 한 배열은 R, G, B 그대로 만들고

다른 배열은 (R, G)를 같은 영역으로 입력받아 그 2개의 배열에 각각 DFS 알고리즘을

수행하여 영역의 갯수를 구하는 방식으로 문제를 풀었습니다.


입력받을때 2가지 배열로 나누어 입력받는 코드입니다.



 for (int i = 0; i < n; i++) {

cin >> str;

for (int j = 0; j < n; j++) {

switch (str[j])

{

case 'R':

arr1[i][j] = 1;

arr2[i][j] = 1;

break;

case 'G':

arr1[i][j] = 2;

arr2[i][j] = 1;

break;

case 'B':

arr1[i][j] = 3;

arr2[i][j] = 2;

break;

}

}

}




영역의 크기를 구해주는 DFS알고리즘 코드 입니다.


 

void DFS(int **arr, int num, int x, int y) {

arr[y][x] = 0;

if (y > 0 && arr[y - 1][x] == num)

DFS(arr, num, x, y - 1);

if (y < n - 1 && arr[y + 1][x] == num)

DFS(arr, num, x, y + 1);

if (x > 0 && arr[y][x - 1] == num)

DFS(arr, num, x - 1, y);

if (x < n - 1 && arr[y][x + 1] == num)

DFS(arr, num, x + 1, y);

}



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
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
 
using namespace std;
int count1 = 0, count2 = 0, n;
 
void DFS(int **arr, int num, int x, int y) {
    arr[y][x] = 0;
    if (y > 0 && arr[y - 1][x] == num)
        DFS(arr, num, x, y - 1);
    if (y < n - 1 && arr[y + 1][x] == num)
        DFS(arr, num, x, y + 1);
    if (x > 0 && arr[y][x - 1== num)
        DFS(arr, num, x - 1, y);
    if (x < n - 1 && arr[y][x + 1== num)
        DFS(arr, num, x + 1, y);
}
 
int main() {
    string str;
    scanf("%d"&n);
    int **arr1 = new int*[n], **arr2 = new int*[n];
 
    for (int i = 0; i < n; i++) {
        arr1[i] = new int[n];
        arr2[i] = new int[n];
    }
 
    for (int i = 0; i < n; i++) {
        cin >> str;
        for (int j = 0; j < n; j++) {
            switch (str[j])
            {
            case 'R':
                arr1[i][j] = 1;
                arr2[i][j] = 1;
                break;
            case 'G':
                arr1[i][j] = 2;
                arr2[i][j] = 1;
                break;
            case 'B':
                arr1[i][j] = 3;
                arr2[i][j] = 2;
                break;
            }
        }
    }
 
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (arr1[i][j] != 0) {
                count1++;
                DFS(arr1, arr1[i][j], j, i);
            }
            if (arr2[i][j] != 0) {
                count2++;
                DFS(arr2, arr2[i][j], j, i);
            }
        }
    }
    printf("%d %d", count1, count2);
    return 0;
}
cs

'알고리즘 문제 > DFS,BFS' 카테고리의 다른 글

[백준] 16234번: 인구 이동 - C++  (0) 2019.06.01
[백준] 1325번: 효율적인 해킹- C++  (0) 2019.05.10

문제 출처:https://www.acmicpc.net/problem/14890



 문제


크기가 N×N인 지도가 있다. 지도의 각 칸에는 그 곳의 높이가 적혀져 있다. 

오늘은 이 지도에서 지나갈 수 있는 길이 몇 개 있는지 알아보려고 한다. 길이란 한 행 또는 한 열 전부를 나타내며, 한쪽 끝에서 다른쪽 끝까지 지나가는 것이다. 

다음과 같은 N=6인 경우 지도를 살펴보자.

이 때, 길은 총 2N개가 있으며, 아래와 같다.

길을 지나갈 수 있으려면 길에 속한 모든 칸의 높이가 모두 같아야 한다. 또는, 경사로를 놓아서 지나갈 수 있는 길을 만들 수 있다. 경사로는 높이가 항상 1이며, 길이는 L이다. 또, 개수는 매우 많아 부족할 일이 없다. 경사로는 낮은 칸과 높은 칸을 연결하며, 아래와 같은 조건을 만족해야한다.

  • 경사로는 낮은 칸에 놓으며, L개의 연속된 칸에 경사로의 바닥이 모두 접해야 한다.
  • 낮은 칸과 높은 칸의 높이 차이는 1이어야 한다.
  • 경사로를 놓을 낮은 칸의 높이는 모두 같아야 하고, L개의 칸이 연속되어 있어야 한다.

아래와 같은 경우에는 경사로를 놓을 수 없다.

  • 경사로를 놓은 곳에 또 경사로를 놓는 경우
  • 낮은 칸과 높은 칸의 높이 차이가 1이 아닌 경우
  • 낮은 지점의 칸의 높이가 모두 같지 않거나, L개가 연속되지 않은 경우
  • 경사로를 놓다가 범위를 벗어나는 경우

L = 2인 경우에 경사로를 놓을 수 있는 경우를 그림으로 나타내면 아래와 같다.

경사로를 놓을 수 없는 경우는 아래와 같다.

위의 그림의 가장 왼쪽부터 1번, 2번, 3번, 4번 예제라고 했을 때, 1번은 높이 차이가 1이 아니라서, 2번은 경사로를 바닥과 접하게 놓지 않아서, 3번은 겹쳐서 놓아서, 4번은 기울이게 놓아서 불가능한 경우이다.

가장 위에 주어진 그림 예의 경우에 지나갈 수 있는 길은 초록색으로, 지나갈 수 없는 길은 빨간색으로 표시되어 있으며, 아래와 같다. 경사로의 길이 L = 2이다.

지도가 주어졌을 때, 지나갈 수 있는 길의 개수를 구하는 프로그램을 작성하시오.


 입력


첫째 줄에 N (2 ≤ N ≤ 100)과 L (1 ≤ L ≤ N)이 주어진다. 둘째 줄부터 N개의 줄에 지도가 주어진다. 각 칸의 높이는 10보다 작거나 같은 자연수이다.


 출력


첫째 줄에 지나갈 수 있는 길의 개수를 출력한다.


이 문제는 N개의 길이 주어졌을때 이 길에서 경사로를 만들지 못하는 조건을 생각해보면 쉽게 풀 수 있습니다.


1. 길의 높이 차이가 2 이상인 경우




이 그림과 같이 높이 차이가 2 이상인경우 경사로를 만들지 못하게 되므로 이 조건에 대한 코드를 작성해줍니다.



 if (abs(vt[i] - vt[i + 1]) > 1)

return 1;




2. L 길이 만큼의 경사로를 만들지 못하는 경우


이 그림과 같이 높이 차이가 1이라 경사로를 만들어야 하지만 L 만큼의 길이 평탄하지 않아 경사로를 만들수 없는 경우도 생각해 주어야 합니다.

저는 count변수를 생성하여 경사로를 만들게 될 L 만큼의 길이 그대로 유지된다면 count변수를 한개씩 증가시키고 최종적으로 count변수가 L과 다른 크기면 길이 평탄하지 않기 때문에 경사로를 만들지 못한다고 판별해 주었습니다.


 

 else if (vt[i] + 1 == vt[i + 1]) {

count = 0;

target = vt[i];

for (int j = i; j > i - l && j >= 0; j--) {

if (vt[j] == target) {

count++;

}

}

if (count != l)

return 1;

}

else if (vt[i] - 1 == vt[i + 1]) {

count = 0;

target = vt[i + 1];

for (int j = i + 1; j <= i + l && j < vt.size(); j++) {

if (vt[j] == target) {

count++;

}

}

for (int j = i + l + 1; j <= i + l*2 && j < vt.size(); j++) {

if (vt[j] > target) {

return 1;

}

}

if (count != l)

return 1;

}



3. 마지막으로 경사로를 만들 수 있지만 경사로가 겹치는 경우를 생각해야 합니다.



이와 같은 경우인데 이 그림을 보면 왼쪽에서 경사로를 만들고 오른쪽에서도 경사로를 만들어 경사로가 서로 겹쳐지게 되면서 결국 경사로를 만들지 못하게 됩니다.

따라서 이 그림의 왼쪽같이 내리막길 경사로를 만들게 될경우 경사로의 끝에서 L 만큼의 길까지 새로운 경사로를 만들지 않는다는것을 확인 해주어야 합니다.


이 3개의 조건만 확인하면서 만약 이 조건중 한개라도 충족이 된다면 그 길은 경사로를 만들지 못하는 길이기 때문에 결과에서 제외시켜주면서 2N개의 모든 길을 전부 확인한다면 쉽게 문제를 풀 수 있습니다. 


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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cmath>
#define INF 100000000
 
using namespace std;
 
int rst;
 
int cal(vector<int> vt, int l) {
    for (int i = 0; i < vt.size()-1; i++) {
        int count = 0, target;
        if (abs(vt[i] - vt[i + 1]) > 1)
            return 1;
        else if (vt[i] + 1 == vt[i + 1]) {
            count = 0;
            target = vt[i];
            for (int j = i; j > i - l && j >= 0; j--) {
                if (vt[j] == target) {
                    count++;
                }
            }
            if (count != l)
                return 1;
        }
        else if (vt[i] - 1 == vt[i + 1]) {
            count = 0;
            target = vt[i + 1];
            for (int j = i + 1; j <= i + l && j < vt.size(); j++) {
                if (vt[j] == target) {
                    count++;
                }
            }
            for (int j = i + l + 1; j <= i + l*2 && j < vt.size(); j++) {
                if (vt[j] > target) {
                    return 1;
                }
            }
            if (count != l)
                return 1;
        }
    }
    return 0;
}
 
int main() {
    vector<int> temp;
    int n, l, **arr;
    scanf("%d %d"&n, &l);
    arr = new int *[n];
    for (int i = 0; i < n; i++)
        arr[i] = new int[n];
    rst = n * 2;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            scanf("%d"&arr[i][j]);
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            temp.push_back(arr[i][j]);
        }
        rst-=cal(temp,l);
        temp.clear();
        for (int j = 0; j < n; j++) {
            temp.push_back(arr[j][i]);
        }
        rst -= cal(temp, l);
        temp.clear();
    }
    
    printf("%d", rst);
    return 0;
}
cs

'알고리즘 문제 > 시뮬레이션' 카테고리의 다른 글

[백준] 2590번: 색종이 - C++  (1) 2019.06.28

문제 출처:https://www.acmicpc.net/problem/2108



 문제


수를 처리하는 것은 통계학에서 상당히 중요한 일이다. 통계학에서 N개의 수를 대표하는 기본 통계값에는 다음과 같은 것들이 있다. 단, N은 홀수라고 가정하자.

  1. 산술평균 : N개의 수들의 합을 N으로 나눈 값
  2. 중앙값 : N개의 수들을 증가하는 순서로 나열했을 경우 그 중앙에 위치하는 값
  3. 최빈값 : N개의 수들 중 가장 많이 나타나는 값
  4. 범위 : N개의 수들 중 최대값과 최소값의 차이

N개의 수가 주어졌을 때, 네 가지 기본 통계값을 구하는 프로그램을 작성하시오.


 입력


첫째 줄에 수의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 그 다음 N개의 줄에는 정수들이 주어진다. 입력되는 정수의 절대값은 4,000을 넘지 않는다.


 출력


첫째 줄에는 산술평균을 출력한다. 소수점 이하 첫째 자리에서 반올림한 값을 출력한다.

둘째 줄에는 중앙값을 출력한다.

셋째 줄에는 최빈값을 출력한다. 여러 개 있을 때에는 최빈값 중 두 번째로 작은 값을 출력한다.

넷째 줄에는 범위를 출력한다.


1. 산술평균은 소수점 첫째 자리에서 반올림 한 값을 출력해야 하기 때문에 N으로 나눌때 double형으로 나누어 주고 printf출력할때 "%.0f" 로 출력해주면 소수점을 생략하고 반올림하여 출력해줍니다. 


2. 중앙값은 N이 홀수이기 때문에 배열이나 벡터를 정렬한뒤 N/2부분을 출력해주면 됩니다.


3. 최빈값은 약간 복잡한데  입력으로 들어오는 정수가 -4000~4000이기 때문에 8001의 크기를 가지는 배열 하나를 선언해주고 -4000~-1까지는 양수로 바꿔주고 바꿔준 값의 배열 인덱스를 +1 해줍니다. 

 1~4000까지는 +4000을 해주고 그 값의 배열 인덱스를 +1 해줍니다.

 이 뒤에 배열의 최대값을 구하고 배열을 전부 탐색하며 최대값을 가진 인덱스를 모두 벡터에 넣어주는데 이 과정에서 1~4000까지는 음수화 해주고 4001~8000까지는 -4000을 해주어야 합니다.

 마지막으로 벡터를 오름차순 정렬한뒤 벡터의 사이즈가 1이면 첫번째 값을 출력해주고 사이즈가 1 이상이면 2번째 값을 출력해주면 됩니다.


4. 최대값과 최소값은 입력을 받을때 구해주는게 가장 좋습니다만 중앙값을 구하기 위해 정렬을 수행했으므로 벡터의 첫번째와 마지막 값을 출력해주면 됩니다.


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
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cmath>
#define INF 100000000
 
using namespace std;
 
bool compare(int a, int b) {
    return a < b;
}
 
int main() {
 
    int n, temp, arr[8001], mx = -1, mode, count = 0;
    double sum = 0;
    vector<int> vt, mode_vt;
    for (int i = 0; i < 8001; i++)
        arr[i] = 0;
    scanf("%d"&n);
    for (int i = 0; i < n; i++) {
        scanf("%d"&temp);
        vt.push_back(temp);
        sum += temp;
        temp = (temp <= 0) ? abs(temp) : temp + 4000;
        arr[temp]++;
        if (arr[temp] > mx) {
            mx = arr[temp];
            mode = temp;
        }
    }
    sort(vt.begin(), vt.end(), compare);
    for (int i = 0; i < 8001; i++) {
        if (arr[i] == mx) {
            mode = i;
            mode = (mode <= 4000) ? -mode : mode - 4000;
            mode_vt.push_back(mode);
        }
    }
    sort(mode_vt.begin(), mode_vt.end(), compare);
    mode = (mode_vt.size() >= 2) ? mode_vt[1] : mode_vt[0];
    double eve = sum / double(n);
    printf("%.0f\n",eve);
    printf("%d\n", vt[n / 2]);
    printf("%d\n", mode);
    printf("%d\n", vt[vt.size()-1- vt[0]);
    
    return 0;
}
cs



문제 출처:https://www.acmicpc.net/problem/15683



 문제


스타트링크의 사무실은 1×1크기의 정사각형으로 나누어져 있는 N×M 크기의 직사각형으로 나타낼 수 있다. 사무실에는 총 K개의 CCTV가 설치되어져 있는데, CCTV는 5가지 종류가 있다. 각 CCTV가 감시할 수 있는 방법은 다음과 같다.

1번2번3번4번5번

1번 CCTV는 한 쪽 방향만 감시할 수 있다. 2번과 3번은 두 방향을 감시할 수 있는데, 2번은 감시하는 방향이 서로 반대방향이어야 하고, 3번은 직각 방향이어야 한다. 4번은 세 방향, 5번은 네 방향을 감시할 수 있다.

CCTV는 감시할 수 있는 방향에 있는 칸 전체를 감시할 수 있다. 사무실에는 벽이 있는데, CCTV는 벽을 통과할 수 없다. CCTV가 감시할 수 없는 영역은 사각지대라고 한다.

CCTV는 회전시킬 수 있는데, 회전은 항상 90도 방향으로 해야 하며, 감시하려고 하는 방향이 가로 또는 세로 방향이어야 한다.

0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 0 6 0
0 0 0 0 0 0

지도에서 0은 빈 칸, 6은 벽, 1~5는 CCTV의 번호이다. 위의 예시에서 1번의 방향에 따라 감시할 수 있는 영역을 '#'로 나타내면 아래와 같다.

0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 # 6 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
# # 1 0 6 0
0 0 0 0 0 0
0 0 # 0 0 0
0 0 # 0 0 0
0 0 1 0 6 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 0 6 0
0 0 # 0 0 0

CCTV는 벽을 통과할 수 없기 때문에, 1번이 → 방향을 감시하고 있을 때는 6의 오른쪽에 있는 벽을 감시할 수 없다.

0 0 0 0 0 0
0 2 0 0 0 0
0 0 0 0 6 0
0 6 0 0 2 0
0 0 0 0 0 0
0 0 0 0 0 5

위의 예시에서 감시할 수 있는 방향을 알아보면 아래와 같다.

0 0 0 0 0 #
# 2 # # # #
0 0 0 0 6 #
0 6 # # 2 #
0 0 0 0 0 #
# # # # # 5
0 0 0 0 0 #
# 2 # # # #
0 0 0 0 6 #
0 6 0 0 2 #
0 0 0 0 # #
# # # # # 5
0 # 0 0 0 #
0 2 0 0 0 #
0 # 0 0 6 #
0 6 # # 2 #
0 0 0 0 0 #
# # # # # 5
0 # 0 0 0 #
0 2 0 0 0 #
0 # 0 0 6 #
0 6 0 0 2 #
0 0 0 0 # #
# # # # # 5
왼쪽 상단 2: ↔, 오른쪽 하단 2: ↔왼쪽 상단 2: ↔, 오른쪽 하단 2: ↕왼쪽 상단 2: ↕, 오른쪽 하단 2: ↔왼쪽 상단 2: ↕, 오른쪽 하단 2: ↕

CCTV는 CCTV를 통과할 수 있다. 아래 예시를 보자.

0 0 2 0 3
0 6 0 0 0
0 0 6 6 0
0 0 0 0 0

위와 같은 경우에 2의 방향이 ↕ 3의 방향이 ←와 ↓인 경우 감시받는 영역은 다음과 같다.

# # 2 # 3
0 6 # 0 #
0 0 6 6 #
0 0 0 0 #

사무실의 크기와 상태, 그리고 CCTV의 정보가 주어졌을 때, CCTV의 방향을 적절히 정해서, 사각 지대의 최소 크기를 구하는 프로그램을 작성하시오.


 입력


첫째 줄에 사무실의 세로 크기 N과 가로 크기 M이 주어진다. (1 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에는 사무실 각 칸의 정보가 주어진다. 0은 빈 칸, 6은 벽, 1~5는 CCTV를 나타내고, 문제에서 설명한 CCTV의 종류이다. 

CCTV의 최대 개수는 8개를 넘지 않는다.


 출력


첫째 줄에 사각 지대의 최소 크기를 출력한다.


해결 방법은 N,M 그리고 CCTV의 개수가 크지 않기 때문에 모든 CCTV 배치를 구해주고 그중 가장 작은 사각지대를 선택하면 되는 비교적 간단한 문제입니다. 

그러나 CCTV의 종류에 따라 감시하는 방향을 각각 설정해줘야 하기 때문에 코드가 상당히 길어지고 모든 CCTV 경우의 수를 구하는게 어렵게 느껴질수도 있습니다.

저도 이 모든 경우의 수를 어떻게 구할까 한참 고민하다 재귀가 가장 구현하기 편할거같아 재귀함수를 이용하여 소스를 작성하였습니다. 



 void cal(vector<CCTV> cv, int index, int state) {

if (index >= cv.size())

return;

if (state > 5)

return;

for (int i = 0; i < n; i++) {

for (int j = 0; j < m; j++) {

if (map[i][j] == 7)

map[i][j] = 0;

}

}


cv[index].state = state;

for (int i = 0; i < cv.size(); i++)

cv[i].check(map);


int temp = count(map);

rst = (rst > temp) ? temp : rst;


cal(cv, index, state + 1);

cal(cv, index + 1, 1);


}



굵게 표시한 부분이 경우의 수를 구해주는 재귀 호출인데 index는 CCTV의 종류를 뜻하고 state는 CCTV의 감시 방향을 나타냅니다.


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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cmath>
#define INF 100000000
 
using namespace std;
int n, m, rst = 0;
int **map;
 
class CCTV {
public:
    int x, y, state, type;
    CCTV(int y, int x, int type) {
        this->= x;
        this->= y;
        this->type = type;    //CCTV의 종류
        this->state = 1;      //CCTV가 보는 방향 1:상,2:우,3:하,4:좌
    }
 
    void up(int **arr){
        for (int i = this->- 1; i >= 0; i--) {
            if (arr[i][this->x] == 6)
                break;
            else if (arr[i][this->x] == 0)
                arr[i][this->x] = 7;
        }
    }
    void right(int **arr) {
        for (int i = this->+ 1; i < m; i++) {
            if (arr[this->y][i] == 6)
                break;
            else if (arr[this->y][i] == 0)
                arr[this->y][i] = 7;
        }
    }
    void down(int **arr) {
        for (int i = this->+ 1; i < n; i++) {
            if (arr[i][this->x] == 6)
                break;
            else if (arr[i][this->x] == 0)
                arr[i][this->x] = 7;
        }
    }
    void left(int **arr) {
        for (int i = this->- 1; i >= 0; i--) {
            if (arr[this->y][i] == 6)
                break;
            else if (arr[this->y][i] == 0)
                arr[this->y][i] = 7;
        }
    }
 
    void check(int **arr) {
        switch (this->type)
        {
        case 1:
            if (this->state == 1) {
                up(arr);
            }
            else if (this->state == 2) {
                right(arr);
            }
            else if (this->state == 3) {
                down(arr);
            }
            else if (this->state == 4) {
                left(arr);
            }
            break;
        case 2:
            if (this->state == 1 || this->state == 3) {
                up(arr);
                down(arr);
            }
            else if (this->state == 2 || this->state == 4) {
                right(arr);
                left(arr);
            }
            break;
        case 3:
            if (this->state == 1) {
                up(arr);
                right(arr);
            }
            else if (this->state == 2) {
                right(arr);
                down(arr);
            }
            else if (this->state == 3) {
                down(arr);
                left(arr);
            }
            else if (this->state == 4) {
                left(arr);
                up(arr);
            }
            break;
        case 4:
            if (this->state == 1) {
                left(arr);
                up(arr);
                right(arr);
            }
            else if (this->state == 2) {
                up(arr);
                right(arr);
                down(arr);
            }
            else if (this->state == 3) {
                right(arr);
                down(arr);
                left(arr);
            }
            else if (this->state == 4) {
                down(arr);
                left(arr);
                up(arr);
            }
            break;
        case 5:
            up(arr);
            left(arr);
            down(arr);
            right(arr);
            break;
        }
    }
 
};
 
int count(int **arr) {
    int temp = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (arr[i][j] == 0)
                temp++;
        }
    }
    return temp;
}
 
void cal(vector<CCTV> cv, int index, int state) {
    
    if (index >= cv.size())
        return;
    if (state > 5)
        return;
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (map[i][j] == 7)
                map[i][j] = 0;
        }
    }
 
    cv[index].state = state;
    for (int i = 0; i < cv.size(); i++)
        cv[i].check(map);
 
    int temp = count(map);
    rst = (rst > temp) ? temp : rst;
 
    cal(cv, index, state + 1);
    cal(cv, index + 11);
 
}
 
int main() {
 
    scanf("%d %d"&n, &m);
    vector<CCTV> cctv;
    map=new int*[n];
 
    for (int i = 0; i < n; i++)
        map[i] = new int[m];
 
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            scanf("%d"&map[i][j]);
            if (1 <= map[i][j] && map[i][j] <= 5) {
                cctv.push_back(CCTV(i, j, map[i][j]));
            }
            else if (map[i][j] == 0)
                rst++;
        }
    }
 
    cal(cctv, 01);
 
    printf("%d", rst);
    return 0;
}
cs

문제 출처:https://www.acmicpc.net/problem/15686



 문제


크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸, 왼쪽에서부터 c번째 칸을 의미한다. r과 c는 1부터 시작한다.

이 도시에 사는 사람들은 치킨을 매우 좋아한다. 따라서, 사람들은 "치킨 거리"라는 말을 주로 사용한다. 치킨 거리는 집과 가장 가까운 치킨집 사이의 거리이다. 즉, 치킨 거리는 집을 기준으로 정해지며, 각각의 집은 치킨 거리를 가지고 있다. 도시의 치킨 거리는 모든 집의 치킨 거리의 합이다.

임의의 두 칸 (r1, c1)과 (r2, c2) 사이의 거리는 |r1-r2| + |c1-c2|로 구한다.

예를 들어, 아래와 같은 지도를 갖는 도시를 살펴보자.

0 2 0 1 0
1 0 1 0 0
0 0 0 0 0
0 0 0 1 1
0 0 0 1 2

0은 빈 칸, 1은 집, 2는 치킨집이다.

(2, 1)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |2-1| + |1-2| = 2, (5, 5)에 있는 치킨집과의 거리는 |2-5| + |1-5| = 7이다. 따라서, (2, 1)에 있는 집의 치킨 거리는 2이다.

(5, 4)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |5-1| + |4-2| = 7, (5, 5)에 있는 치킨집과의 거리는 |5-5| + |4-5| = 1이다. 따라서, (5, 4)에 있는 집의 치킨 거리는 1이다.

이 도시에 있는 치킨집은 모두 같은 프랜차이즈이다. 프렌차이즈 본사에서는 수익을 증가시키기 위해 일부 치킨집을 폐업시키려고 한다. 오랜 연구 끝에 이 도시에서 가장 수익을 많이 낼 수 있는  치킨집의 개수는 최대 M개라는 사실을 알아내었다.

도시에 있는 치킨집 중에서 최대 M개를 고르고, 나머지 치킨집은 모두 폐업시켜야 한다. 어떻게 고르면, 도시의 치킨 거리가 가장 작게 될지 구하는 프로그램을 작성하시오.


 입력


첫째 줄에 N(2 ≤ N ≤ 50)과 M(1 ≤ M ≤ 13)이 주어진다.

둘째 줄부터 N개의 줄에는 도시의 정보가 주어진다.

도시의 정보는 0, 1, 2로 이루어져 있고, 0은 빈 칸, 1은 집, 2는 치킨집을 의미한다. 집의 개수는 2N개를 넘지 않으며, 적어도 1개는 존재한다. 치킨집의 개수는 M보다 크거나 같고, 13보다 작거나 같다.


 출력


첫째 줄에 폐업시키지 않을 치킨집을 최대 M개를 골랐을 때, 도시의 치킨 거리의 최소값을 출력한다.



이 문제는 


1.입력으로 들어오는 집과 치킨의 좌표를 각각 다른 벡터에 넣어준다.

2.모든 치킨집에서 M개의 치킨집을 뽑는 모든 조합을 구해준다.

3.구해진 모든 조합의 '도시의 치킨 거리'를 구하고 그중 가장 작은 값을 출력해준다.


의 과정을 거쳐 해결하였습니다.


1번에서 벡터를 쓴 이유는 문제의 테스트 케이스에서 집과 치킨집이 몇개 들어오는지 확실하지 않기 때문입니다. 


2번에서 "모든 경우의 수를 구하면 너무 많은 조합이 나오지 않을까?" 하실수도 있습니다. 

하지만 문제를 읽어보면 치킨집은 1~13개 밖에 안되고 따라서 모든 경우의 수를 구해도 그 수가 적다는 것을 알 수 있습니다.

저는 재귀를 이용한 조합 알고리즘을 이용하여 모든 조합을 구해주었습니다 아래가 그 코드입니다. 



 void combination(int arr[], int index, int n, int k, int target) {          //치킨집의 모든 조합 구해주는 함수

if (k == 0) {

int temp = select(arr);

rst = (temp < rst) ? temp : rst;           //새로 구해진 치킨거리가 더 적으면 결과값을 바꿔준다

}

else if (target == n)

return;

else {

arr[index] = target;

combination(arr, index + 1, n, k - 1, target + 1);

combination(arr, index, n, k, target + 1);

}

}



마지막으론 구해진 모든 치킨집 조합에 대해 '도시의 치킨 거리'를 구하고 그중 가장 작은 값을 선택하면 됩니다. 


문제에서 구해야 할 것이 많아 복잡해 보일 수 있지만 그 과정을 한 단계씩 수행 해본다면 생각보다 어렵지 않다는것을 알 수 있을 것 입니다.


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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cmath>
#define INF 100000000
 
using namespace std;
 
class COORD {            //좌표 저장하기 위한 클래스
public:
    int x, y;
    COORD(int y, int x) {
        this->= x;
        this->= y;
    }
};
 
int rst = INF, n, m;
vector<COORD> house, chicken;
 
int select(int arr[]) {     //구해진 조합을 가지고 집과의 거리를 비교하고 도시의 치킨 거리를 구해주는 함수
    int sum = 0;
    for (int i = 0; i < house.size(); i++) {
        int distance = INF;
        for (int j = 0; j < m; j++) {
            int temp = abs(house[i].x - chicken[arr[j]].x) + abs(house[i].y - chicken[arr[j]].y);
            distance = (temp < distance) ? temp : distance;
        }
        sum += distance;
    }
    return sum;
}
 
void combination(int arr[], int index, int n, int k, int target) {          //치킨집의 모든 조합 구해주는 함수(조합)
    if (k == 0) {
        int temp = select(arr);
        rst = (temp < rst) ? temp : rst;           //새로 구해진 치킨거리가 더 적으면 결과값을 바꿔준다
    }
    else if (target == n)
        return;
    else {
        arr[index] = target;
        combination(arr, index + 1, n, k - 1, target + 1);
        combination(arr, index, n, k, target + 1);
    }
}
 
int main() {
 
    scanf("%d %d"&n, &m);
 
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            int temp;
            scanf("%d"&temp);
            if (temp == 2)
                chicken.push_back(COORD(i, j));
            if (temp == 1)
                house.push_back(COORD(i, j));
        }
    }
    int *arr = new int[m];
 
    combination(arr, 0, chicken.size(), m, 0);
    
    printf("%d", rst);
 
    return 0;
}
cs


'알고리즘 문제 > 브루트 포스' 카테고리의 다른 글

[백준] 14697번: 방 배정하기 - C++  (0) 2019.05.28
[백준] 15683번: 감시 - C++  (0) 2018.07.20

문제 출처:https://www.acmicpc.net/problem/11404



 문제


n(1≤n≤100)개의 도시가 있다. 그리고 한 도시에서 출발하여 다른 도시에 도착하는 m(1≤m≤100,000)개의 버스가 있다. 각 버

스는 한 번 사용할 때 필요한 비용이 있다.

모든 도시의 쌍 (A, B)에 대해서 도시 A에서 B로 가는데 필요한 비용의 최소값을 구하는 프로그램을 작성하시오.


입력


첫째 줄에 도시의 개수 n(1≤n≤100)이 주어지고 둘째 줄에는 버스의 개수 m(1≤m≤100,000)이 주어진다. 그리고 셋째 줄부  

터 m+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 버스의 출발 도시의 번호가 주어진다. 버스의 정보는 

버스의 시작 도시 a, 도착 도시 b, 한 번 타는데 필요한 비용 c로 이루어져 있다. 시작 도시와 도착 도시가 같은 경우는 없다. 비

용은 100,000보다 작거나 같은 자연수이다.


 출력


N개의 줄을 출력해야 한다. i번째 줄에 출력하는 j번째 숫자는 도시 i에서 j로 가는데 필요한 최소 비용이다. 만약, i에서 j로 갈 수 없는 경우에는 그 자리에 0을 출력한다.


이 문제는 모든 정점의 쌍(A,B)에 대해서 A에서 B로 가는데 필요한 비용의 최소값을 구하는 문제입니다.

따라서 그래프의 가능한 모든 노드 쌍에 대해 최단 거리를 구하는 알고리즘인 플로이드-워셜 알고리즘을 사용한다면 쉽게 풀수 있습니다.

플로이드 워셜 알고리즘의 시간 복잡도는 O(n³)인데 문제의 n은 최대 100 이므로 제한 시간인 1초 이내에 충분히 풀 수 있다는것을 알 수 있습니다. 


이 문제에선 추가적으로 처리해줘야 할 부분이 두가지 있는데 그중 첫번째는 중복되는 노드의 값이 들어온다는 것입니다.

출처로 들어가 문제의 예제 입력부분을 확인하면 1번에서 4번 노드로 가는 값이 2번 중복되는 것을 볼 수 있습니다.

따라서 중복되는 노드값이 입력될때 비용이 더 적은 값으로 바꿔주어야 합니다.

 for (int i = 0; i < m; i++) {

int a, b, c;

scanf("%d %d %d", &a, &b, &c);

if (adj[a - 1][b - 1] > c)

adj[a - 1][b - 1] = c;

}

저는 입력부분에 위 코드를 추가해줌으로써 첫번째 문제를 해결하였습니다.


두번째 문제는 출처에 들어가 출력부분을 읽어보면 "만약, i에서 j로 갈 수 없는 경우에는 그 자리에 0을 출력한다." 라는 말이 있습니다.

저는 서로 갈 수 없는 노드는 INF로 처리했기 때문에 플로이드 워셜 알고리즘을 수행한 후에 출력부분에 INF를 0으로 바꿔주는 코드를 추가했습니다. 

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

if (adj[i][j] == INF)

adj[i][j] = 0;

printf("%d ", adj[i][j]);

}

printf("\n");

}

밑줄친 부분이 추가해준 코드입니다.


플로이드 워셜 알고리즘이란 - https://ko.wikipedia.org/wiki/%ED%94%8C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%9B%8C%EC%85%9C_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98


전체 코드입니다.

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
#pragma warning(disable:4996)
#include <iostream>
#include <cstdlib>
#define INF 100000000
 
 
using namespace std;
 
int main() {
    int n, m;
    scanf("%d %d"&n, &m);
    int **adj = new int*[n];
 
    for (int i = 0; i < n; i++)
        adj[i] = new int[n];
 
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            adj[i][j] = (i == j) ? 0 : INF;
        }
    }
 
    for (int i = 0; i < m; i++) {
        int a, b, c;
        scanf("%d %d %d"&a, &b, &c);
        if (adj[a - 1][b - 1> c)
            adj[a - 1][b - 1= c;
    }
 
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                adj[i][j] = (adj[i][j] < (adj[i][k] + adj[k][j])) ? adj[i][j] : adj[i][k] + adj[k][j];
            }
        }
    }
 
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (adj[i][j] == INF)
                adj[i][j] = 0;
            printf("%d ", adj[i][j]);
        }
        printf("\n");
    }
 
    return 0;
}
cs

문제 출처:https://www.acmicpc.net/problem/1629



 문제


자연수 A를 B번 곱한 수를 알고 싶다. 단 구하려는 수가 매우 커질 수 있으므로 이를 C로 나눈 나머지를 구하는 프로그램을 작성하시오.


 입력


첫째 줄에 A, B, C가 빈 칸을 사이에 두고 순서대로 주어진다. A, B, C는 모두 2,147,483,647 이하의 자연수이다.


 출력


첫째 줄에 A를 B번 곱한 수를 C로 나눈 나머지를 출력한다.


이 문제는 얼핏보면 그냥 A의 제곱을 구하는 간단한 문제로 보입니다. 그러나 막상 문제를 풀어보면 2가지 문제점을 발견하게 됩니다. 


1. 숫자의 범위가 자료형의 표현 범위보다 커진다.

2. O(n)의 시간복잡도로 문제를 풀어도 시간제한에 걸린다.  


1번의 해결방법으론 long long 자료형을 사용하는 것입니다.

그러나 이러한 자료형을 사용하더라도 문제는 제곱수이기 때문에 적은 연산에도 숫자가 기하급수적으로 커지게 되고 결과적으로 long long자료형의 범위도 넘어가버리게 됩니다.

따라서 연산을 할때마다 C로 나눈 나머지로 값을 바꿔주어야 자료형의 범위를 넘지 않고 계산이 가능합니다.


2번은 B의 크기가 최대 2,147,483,647인데 이 수를 for문을 사용하여 그대로 2,147,483,647번 반복한다면 대략 1억번의 반복을 1초라 생각했을때 21초나 걸리게 되어 문제의 시간제한인 2초를 훨씬 넘기게 되버립니다.

따라서 이 문제는 for문을 사용하지 않고 함수의 재귀를 이용하는 분할정복 알고리즘을 이용하여 풀어야 O(log n)의 시간복잡도를 가지게 되고 문제의 최대값이 들어와도 바로 결과값을 나타낼수 있습니다.


분할정복 알고리즘이란 https://ko.wikipedia.org/wiki/%EB%B6%84%ED%95%A0_%EC%A0%95%EB%B3%B5_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98


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
#include <stdio.h>
#include <stdlib.h>
 
long long int a, b, c;
int result = 0;
long long int pow(long long int x, long long int m) {
 
    if (m == 0)
        return 1;
    else if (m == 1)
        return x;
    if (m % 2 > 0)
        return pow(x, m - 1)*x;
    long long int half = pow(x, m / 2);
    half %= c;
    return (half * half) % c;
}
 
int main() {
 
    scanf("%lld %lld %lld"&a, &b, &c);
 
    printf("%lld", pow(a, b) % c);
 
    return 0;
}
cs


+ Recent posts