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

 

15665번: N과 M (11)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해

www.acmicpc.net

 

 

문제접근 및 풀이

기본적인 백트래킹문제이다.

 

고려해야할 사항이 몇가지 있다.

 

1. 중복된 경우를 허용하지 않는다.

-> mul이라는 변수를 사용해서 바로 이전에 선택한 숫자를 저장하여 for문 안에서 mul과 겹친다면 넘어가도록 하여 중복을 없앤다.

 

2. 같은 숫자를 여러번 사용가능하다.

-> 백트래킹시에, for문의 변수 i 를 0부터 시작하여 같은 숫자를 여러번 사용 가능하도록 한다.

 

3. 사전 순으로 증가하는 순서로 출력해야 한다.

-> input 배열을 sort하고 백트래킹시에 0번 인덱스부터 접근한다.

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

using namespace std;

int n,m;
vector<int> input;
vector<int> result;

void bt(int depth){
  if(depth==m){
    for(int i=0; i<result.size(); i++){
      printf("%d ", result[i]);
    }
    printf("\n");
    return ;
  }
  int mul = -1;
  for(int i=0; i<n; i++){
    if(mul == input[i]) continue;
    mul = input[i]; 
    result.push_back(input[i]);
    bt(depth+1);
    result.pop_back();
  }
  return ;
}

int main(){
  cin >> n >> m;
  for(int x,i=0; i<n; i++){
    scanf("%d", &x);
    input.push_back(x);
  }
  sort(input.begin(), input.end());
  bt(0);
  return 0;
}

느낀점

단순히 조합의 경우들을 출력하는 문제라고 생각하고 얉보아 덤볐다가 한대 얻어 맞은 문제이다.

중복을 해결하기가 쉽지 않았다. 처음부터 로직을 한단계 한단계 이해하고 생각해야 했다. 덕분에 기초를 다시 다지는 기회를 가졌다.

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

 

20040번: 사이클 게임

사이클 게임은 두 명의 플레이어가 차례대로 돌아가며 진행하는 게임으로, 선 플레이어가 홀수 번째 차례를, 후 플레이어가 짝수 번째 차례를 진행한다. 게임 시작 시 0 부터 n − 1 까지 고유한

www.acmicpc.net

문제

사이클 게임은 두 명의 플레이어가 차례대로 돌아가며 진행하는 게임으로, 선 플레이어가 홀수 번째 차례를, 후 플레이어가 짝수 번째 차례를 진행한다. 게임 시작 시 0 부터 n − 1 까지 고유한 번호가 부여된 평면 상의 점 n 개가 주어지며, 이 중 어느 세 점도 일직선 위에 놓이지 않는다. 매 차례 마다 플레이어는 두 점을 선택해서 이를 연결하는 선분을 긋는데, 이전에 그린 선분을 다시 그을 수는 없지만 이미 그린 다른 선분과 교차하는 것은 가능하다. 게임을 진행하다가 처음으로 사이클을 완성하는 순간 게임이 종료된다. 사이클 C는 플레이어가 그린 선분들의 부분집합으로, 다음 조건을 만족한다.

C에 속한 임의의 선분의 한 끝점에서 출발하여 모든 선분을 한 번씩만 지나서 출발점으로 되돌아올 수 있다.

문제는 선분을 여러 개 그리다 보면 사이클이 완성 되었는지의 여부를 판단하기 어려워 이미 사이클이 완성되었음에도 불구하고 게임을 계속 진행하게 될 수 있다는 것이다. 이 문제를 해결하기 위해서 게임의 진행 상황이 주어지면 몇 번째 차례에서 사이클이 완성되었는지, 혹은 아직 게임이 진행 중인지를 판단하는 프로그램을 작성하려 한다.

입력으로 점의 개수 n과 m 번째 차례까지의 게임 진행 상황이 주어지면 사이클이 완성 되었는지를 판단하고, 완성되었다면 몇 번째 차례에서 처음으로 사이클이 완성된 것인지를 출력하는 프로그램을 작성하시오.

입력

입력은 표준입력을 사용한다. 입력의 첫 번째 줄에는 점의 개수를 나타내는 정수 3 ≤ n ≤ 500,000 과 진행된 차례의 수를 나타내는 정수 3 ≤ m ≤ 1,000,000 이 주어진다. 게임에서 사용하는 n개의 점에는 0 부터 n − 1 까지 고유한 번호가 부여되어 있으며, 이 중 어느 세 점도 일직선 위에 놓이지 않는다. 이어지는 m 개의 입력 줄에는 각각 i번째 차례에 해당 플레이어가 선택한 두 점의 번호가 주어진다 (1 ≤ i ≤ m).

출력

출력은 표준출력을 사용한다. 입력으로 주어진 케이스에 대해, m 번째 차례까지 게임을 진행한 상황에서 이미 게임이 종료되었다면 사이클이 처음으로 만들어진 차례의 번호를 양의 정수로 출력하고, m 번의 차례를 모두 처리한 이후에도 종료되지 않았다면 0을 출력한다.

 

문제접근 및 풀이

- union find 알고리즘을 이용해 해결했다.

- 간선정보들을 받으면서 싸이클이 생기는 최소의 순간, 즉 x의 부모와 y의 부모가 같고 result 값이 아직 0으로, 갱신이 발생하지 않은 상황에서 result 값을 i+1로 만들어준다.

#include <iostream>

using namespace std;

int n,m;
int par[500000]={0};

int getpar(int x){
  if(par[x]==x) return x;
  return par[x] = getpar(par[x]);
}

int main(){
  cin >> n >> m;
  int result=0;
  for(int i=0; i<n; i++) par[i]=i;
  for(int x,y,i=0; i<m; i++){
    scanf("%d %d", &x, &y);
    int fx = getpar(x);   
    int fy = getpar(y);
    if(fx == fy && result==0){
      result = i+1;
    }
    par[fx] = fy;
  }
  if(result) cout << result;
  else cout << 0;
}

 

느낀점

union_find algorithm을 리마인딩할 수 있어서 좋았다.

'알고리즘 문제풀이' 카테고리의 다른 글

[백준] 15663 N과 M (9) c++  (0) 2021.09.04
[백준] 15665번 N과 M (11) c++  (0) 2021.09.04
[백준] 20046번 Road Reconstruction c++  (0) 2021.08.30
[백준] 1799번 비숍 c++  (0) 2021.08.28
[백준] 16509번 장군 c++  (0) 2021.08.27

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

문제

홍수의 발생으로 인해 도시의 도로들이 유실되어 많은 ICP(International Computational Plan) 시민들이 불편을 겪고 있다. 도시는 아래와 같은 격자 형태로 표현이 된다고 가정하자. 검정색 단위 격자가 ‘단위도로’를 의미하며 흰색 단위 격자는 도로가 없었거나 유실된 상태를 의미한다. X로 표시된 단위 격자는 도로를 건설할 수 없는 곳을 의미한다.

도시의 차들은 단위 도로 상에서 가로나 세로 방향으로만 움직인다고 가정했을 때 도시의 기능을 회복시키기 위해 맨 왼쪽 위 단위 격자에서 맨 오른쪽 아래 단위 격자로 도시의 차들이 가는 경로를 만들기 위해 필요한 최소한의 도로 건설 비용을 계산하고자 한다. 단위 도로 하나를 건설하기 위해 1 또는 2 의 비용이 소요된다고 가정하자. 위 그림에서는 흰색 단위 격자에 단위 도로 하나를 건설하기 위해서는 1 의 비용이 든다고 가정한다.

위와 같이 회색으로 표시된 단위 도로들을 4 의 비용으로 건설하면 목적을 달성할 수 있다.

도시가 위와 같이 격자 형태로 주어졌을 때 맨 왼쪽 위 단위 격자에서 맨 오른쪽 아래 단위 격자로 가는 경로를 만들기 위해 필요한 최소 도로 건설 비용을 구하는 프로그램을 작성하시오.

입력

입력은 표준입력을 사용한다. 첫 번째 줄에 도시를 표현하는 격자의 행과 열의 크기를 각각 나타내는 두 개의 양의 정수 m, n (1 ≤ m, n ≤ 1,000, 1 < m×n)이 주어진다. 다음 m개의 각 줄에 격자의 각 열의 정보를 나타내는 n개의 숫자가 주어진다. 각 열의 정보는 정수 0, 1, 2, -1 로 나타내며 0 은 단위도로가 이미 존재하는 것을, 즉, 도로에 유실이 없는 상태, 1 은 단위 도로가 없고 1 의 비용으로 도로를 건설할 수 있는 단위 격자, 2 는 단위 도로가 없고 2 의 비용으로 도로를 건설할 수 있는 단위 격자를 의미한다. -1 은 X로 표시된 단위 격자로 그 위치에 단위 도로를 건설할 수 없는 상태를 의미한다.

출력

출력은 표준출력을 사용한다. 도시의 맨 왼쪽 위 단위 격자에서 맨 오른쪽 아래 단위 격자로 가는 경로를 만들기 위해 필요한 최소한의 도로 건설 비용을 한 줄에 출력한다. 해당 경로를 건설할 수 없을 때는 -1 을 출력한다.

 

문제접근 및 풀이

- BFS와 다익스트라 알고리즘을 응용하여 해결하였다.

- 우선순위 큐를 사용하여 비용이 가장 낮은 경로를 우선적으로 탐색하도록 했다.

- 따라서 탐색중 (n-1,m-1)에 처음 도달한 순간의 cost가 답이된다.

- 탐색을 실패하고 큐가 비게되면 가능한 경로가 없는 것으로 -1을 출력한다.

#include <iostream>
#include <queue>
#define MAX 1000

using namespace std;

int n,m;
int b[MAX][MAX];

struct P{
    int x;
    int y;
    int c;
};

struct cmp{
    bool operator()(P a, P b){
        return a.c > b.c; // 작은 것부터 빼야하므로 오름차순정렬 즉 앞이 커 클때 반환
    }
};

int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};

int bfs(){
    int v[MAX][MAX]={0};
    priority_queue<P, vector<P>, cmp> pq;
    
    int start = b[0][0];
    if(start==-1) return -1;
    if(start==0) pq.push({0,0,0});
    if(start==1) pq.push({0,0,1});
    if(start==2) pq.push({0,0,2});
    v[0][0]=1;
    
    while(!pq.empty()){
        P p = pq.top();
        int x = p.x;
        int y = p.y;
        int c = p.c;
        pq.pop();
        //printf("x:%d, y:%d, c:%d\n",x,y,c);
        if(x==n-1 && y==m-1){
            return c;
        }
        
        for(int i=0; i<4; i++){
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(nx >= 0 && ny >= 0 && nx < n && ny < m){
                int k = b[nx][ny];
                if(v[nx][ny]==0 && b[nx][ny]!=-1){ // 방문안했고 다리 못놓는 자리가 아니라면 건설 or 경유
                    v[nx][ny]=1; // v[nx][ny]==1; 로 했더니 방문체크가 안되서 계속 돌고도는 현상 발생
                    if(k==0){
                        pq.push({nx,ny,c});
                    }
                    if(k==1){
                        pq.push({nx,ny,c+1});
                    }
                    if(k==2){
                        pq.push({nx,ny,c+2});
                    }
                }
                
            }
        }
    }
    return -1;
}

int main()
{
    cin >> n >> m;
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            scanf("%d", &b[i][j]);
        }
    }
    cout << bfs();
    return 0;
}

 

느낀점

시간복잡도를 보아하니 bfs단순 탐색만으로는 시간초과가 날것같아서, priority queue를 바로 떠올려 해결한점이 뿌듯하다. 다른 코드를 찾아보니 단순 bfs 탐색을 한경우 시간초과가 났다고 한다.

'알고리즘 문제풀이' 카테고리의 다른 글

[백준] 15665번 N과 M (11) c++  (0) 2021.09.04
[백준] 20040번 사이클 게임 c++  (0) 2021.08.30
[백준] 1799번 비숍 c++  (0) 2021.08.28
[백준] 16509번 장군 c++  (0) 2021.08.27
[백준] 14722번 우유 도시 c++  (0) 2021.08.26

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

 

1799번: 비숍

첫째 줄에 체스판의 크기가 주어진다. 체스판의 크기는 10이하의 자연수이다. 둘째 줄부터 아래의 예와 같이 체스판의 각 칸에 비숍을 놓을 수 있는지 없는지에 대한 정보가 체스판 한 줄 단위로

www.acmicpc.net

 

문제

서양 장기인 체스에는 대각선 방향으로 움직일 수 있는 비숍(bishop)이 있다. < 그림 1 >과 같은 정사각형 체스판 위에 B라고 표시된 곳에 비숍이 있을 때 비숍은 대각선 방향으로 움직여 O로 표시된 칸에 있는 다른 말을 잡을 수 있다.

< 그림 1 >

그런데 체스판 위에는 비숍이 놓일 수 없는 곳이 있다. < 그림 2 >에서 체스판에 색칠된 부분은 비숍이 놓일 수 없다고 하자. 이와 같은 체스판에 서로가 서로를 잡을 수 없도록 하면서 비숍을 놓는다면 < 그림 3 >과 같이 최대 7개의 비숍을 놓을 수 있다.  색칠된 부분에는 비숍이 놓일 수 없지만 지나갈 수는 있다.

< 그림 2 >

< 그림 3 >

정사각형 체스판의 한 변에 놓인 칸의 개수를 체스판의 크기라고 한다. 체스판의 크기와 체스판 각 칸에 비숍을 놓을 수 있는지 없는지에 대한 정보가 주어질 때, 서로가 서로를 잡을 수 없는 위치에 놓을 수 있는 비숍의 최대 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 체스판의 크기가 주어진다. 체스판의 크기는 10이하의 자연수이다. 둘째 줄부터 아래의 예와 같이 체스판의 각 칸에 비숍을 놓을 수 있는지 없는지에 대한 정보가 체스판 한 줄 단위로 한 줄씩 주어진다. 비숍을 놓을 수 있는 곳에는 1, 비숍을 놓을 수 없는 곳에는 0이 빈칸을 사이에 두고 주어진다.

출력

첫째 줄에 주어진 체스판 위에 놓을 수 있는 비숍의 최대 개수를 출력한다.

 

 

접근방법 및 풀이

- 백트레킹으로 해결하였다.

처음에는 단순히 모든 경우를 고려하는 방법으로 코드를 짰다가 시간이 10초임에도 불구하고 시간초과가 났다.

생각을 거듭해도 진전이 보이지 않아 구글링을 하여 힌트를 얻었다.

보드판의 흰칸과 검은칸은 서로 영향을 받지 않으므로 두가지 경우로 나누어 탐색을 진행하면 복잡도가 2^(N*N)에서 2^(n/2 * n/2)로 줄어든다는 것을 알게되었다.

비숍의 이동특성 상 흰/검은 칸이 독립적인 것이라는을 생각해 내기가 어려웠다.

 

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

using namespace std;

int n;
int b[11][10];
int v[11][10]={0};
int result=0;
int r[2]={0};

int dx[4]={1,1,-1,-1};
int dy[4]={-1,1,1,-1};

//-1 비숍
//0 빈자리
//1 못놓는자리

bool check(int x, int y, int res[][10]){
    for(int i=1; i<n; i++){
        for(int j=0; j<4; j++){
            int nx = x + dx[j]*i;
            int ny = y + dy[j]*i;
            if(nx>=0 && ny>=0 && nx < n && ny < n){
                if(res[nx][ny]==-1){
                    return false;
                }
            }
        }
    }
    return true;
}

void bt(int x, int y, int res[][10], int c, int k, vector<pair<int, int>> a){
    if(x==n){
        r[k] = max(r[k], c);
        return ;
    }
    if(y==n-1) bt(x+1,0,res,c,k,a);
    else bt(x,y+1,res,c,k,a);
    
    
    if(res[x][y]==0) return ;//디버깅 : 얘가 맨 위에 있으면 비숍 놓지않고 탐색하는 경우가 막히게 된다.
    if(v[x][y]==k) return ;
    
    if(check(x,y,res)){
        res[x][y]=-1;
        a.push_back({x,y});
        if(y==n-1) bt(x+1,0,res,c+1,k,a);
        else bt(x,y+1,res,c+1,k,a);
        res[x][y]=1;
        a.pop_back();
    }
    return ;
}

int main()
{
    cin >> n;
    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++){
            scanf("%d", &b[i][j]);
            if((i+j)%2==0) v[i][j]=1;
        }
    }
    vector<pair<int, int>> a;
    bt(0,0,b,0,0, a);
    bt(0,0,b,0,1,a);
    cout << r[0]+r[1];
    return 0;
}

 

느낀점

끈질기게 반례를 찾아서 결국 무엇이 문제인지 파악하고 해결했다. 너무 뿌듯하고 덕분에 자신감이 생겼다. 항상 중간에 걸리는 케이스가 있어서 틀리게 되면 반례를 직접 만들생각은 안하고 찾아다니기만 하는 나 자신이었는데 이제는 스스로 반례를 만들어 끝까지 해결해야 겠다는 마인드가 생겼다.

 탐색 종료조건을 처음에 x,y == n-1,n-1로 해서 마지막칸이 비숍을 놓을 수 있는 경우에 비숍을 놓지못하고 종료되었다. 이를 해결하기 위해 배열 한줄을 더 여유롭게 [11][10]으로 해서 x==n일때 탐색을 종료하는 것으로 수정하였다.
 

 

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

 

16509번: 장군

오랜만에 휴가를 나온 호근이는 문득 동아리방에 있는 장기가 하고 싶어졌다. 하지만 장기를 오랫동안 하지 않은 탓인지 예전에는 잘 쓰던 상을 제대로 쓰는 것이 너무 힘들었다. 호근이를 위해

www.acmicpc.net

 

문제

오랜만에 휴가를 나온 호근이는 문득 동아리방에 있는 장기가 하고 싶어졌다. 하지만 장기를 오랫동안 하지 않은 탓인지 예전에는 잘 쓰던 상을 제대로 쓰는 것이 너무 힘들었다. 호근이를 위해 상을 어떻게 써야 할지 도와주자.

위 그림은 10×9 크기의 장기판을 나타내며, 상은 (5, 4)에, 왕은 (1, 4)에 자리 잡고 있는 기물이다. (0, 3)과 (2, 5)를 꼭짓점으로 하는 사각형과, (7, 3)과 (9, 5)를 꼭짓점으로 하는 사각형은 왕이 위치할 수 있는 궁성이라고 한다. 상은 위 그림과 같이 8가지 방법으로 움직일 수 있는데, 상, 하, 좌, 우로 한 칸을 이동한 후에 같은 방향 쪽 대각선으로 두 칸 이동한다.

만약 상이 이동하는 경로에 위 그림과 같이 다른 기물이 있다면 상은 그쪽으로 이동할 수 없다. 또한, 상이 장기판을 벗어날 수도 없다.

10×9 크기의 장기판 위에 상과 왕의 처음 위치가 주어졌을 때, 상이 왕에게 도달할 수 있는 최소 이동 횟수를 구하여라.

입력

첫 번째 줄에는 상의 위치를 의미하는 정수 R1, C1이 주어진다.

두 번째 줄에는 왕의 위치를 의미하는 정수 R2, C2가 주어진다. 장기판에서 Ri (0 ≤ Ri ≤ 9)는 행을, Ci (0 ≤ Ci ≤ 8)는 열을 의미한다.

왕은 항상 궁성에 자리 잡고 있으며, 상과 왕의 위치는 겹치지 않는다.

출력

상이 왕에게 도달할 수 있는 최소 이동 횟수를 출력한다. 만약 도달할 수 없다면 -1을 출력한다.

 

문제접근 및 풀이

- BFS알고리즘으로 해결하였다.

 

BFS 탐색 과정 중 유의점

- 상이 움직일 수 있는 8가지를 탐색한다.

- 이때 움직이는 경로 중에 기물(왕)이 있으면 해당 경로로 이동하지 못한다.

#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
#define VMAX 1000
#define INF 987654321

using namespace std;

pair<int,int> from;
pair<int,int> to;

int dx[8] = {3,3,2,-2,-3,-3,-2,2};
int dy[8] = {2,-2,-3,-3,-2,2,3,3};

bool check(int x,int y,int i){
  int tx = to.first;
  int ty = to.second;
  if(i==0)
    if((tx==x+1&&ty==y)||(tx==x+2&&ty==y+1)) return false;
  if(i==1)
    if((tx==x+1&&ty==y)||(tx==x+2&&ty==y-1)) return false;
  if(i==2)
    if((tx==x&&ty==y-1)||(tx==x+1&&ty==y-2)) return false;
  if(i==3)
    if((tx==x&&ty==y-1)||(tx==x-1&&ty==y-2)) return false;
  if(i==4)
    if((tx==x-1&&ty==y)||(tx==x-2&&ty==y-1)) return false;
  if(i==5)
    if((tx==x-1&&ty==y)||(tx==x-2&&ty==y+1)) return false;
  if(i==6)
    if((tx==x&&ty==y+1)||(tx==x-1&&ty==y+2)) return false;
  if(i==7){
    if((tx==x&&ty==y+1)||(tx==x+1&&ty==y+2)) return false;
    }
  return true;
}
int bfs(){
  int v[10][9]={0};
  queue<pair<pair<int,int>,int>> q;
  q.push({{from.first, from.second}, 0});
  v[from.first][from.second] = 1;
  while(!q.empty()){
    int x = q.front().first.first;
    int y = q.front().first.second;
    int c = q.front().second;
    q.pop();
    if(x==to.first && y==to.second){
      return c;
    }
    for(int i=0; i<8; i++){
      int nx = x + dx[i];
      int ny = y + dy[i];
      if(nx>=0 && ny>=0 && nx < 10 && ny < 9 && v[nx][ny]==0){
        if(check(x,y,i)==true){
          //printf("x:%d, y:%d, c:%d, i:%d\n",x,y,c,i);
          v[nx][ny]=1;
          q.push({{nx,ny}, c+1});
        }
      }
    }

  }

  return -1;
}

int main(){
  cin >> from.first >> from.second;
  cin >> to.first >> to.second;

  cout << bfs();

  return 0;
}

 

느낀점

- 시뮬레이션 요소가 들어가 있어서 새롭게 다가온 BFS문제였다. 간단히 해결할 수 있을 거라고 생각해서 바로 코딩에 옮겼지만 8가지 경로에 대해 왕이 중간에 있는지 체크하는 것을 구현하느라 시간이 좀 걸렸다. 이 과정을 좀 정리한 후에 코딩했으면 더 순조롭게 풀렸을 것 같다.

'알고리즘 문제풀이' 카테고리의 다른 글

[백준] 20046번 Road Reconstruction c++  (0) 2021.08.30
[백준] 1799번 비숍 c++  (0) 2021.08.28
[백준] 14722번 우유 도시 c++  (0) 2021.08.26
[백준] 14719번 빗물 c++  (0) 2021.08.26
[백준] 14716번 현수막 c++  (0) 2021.08.26

2. 400년의 축적

2-1. 보편만능 기계의 탄생

과거 수학자들은 계속해서 발전해나가는 수학적인 발견들을 되돌아보며 어쩌면 기계적인 방식, 즉 자동적으로 수학의 모든 사실을 술술 만들어낼 수 있을것 같다는 꿈을 꾸고 있었다.

 

1931년 쿠르트 괴델이 내린 증명은 수학계의 이러한 꿈을 산산이 깨뜨려놓았다.

기계적인 방식으로는 수학의 모든 사실을 만들어 낼 수 없다

 

불완전성 정리

괴델의 증명은 대략 이러한 방식이다.

기계적인 방식만으로 참과 거짓을 판단할 수 없는 명제가 항상 존재함을 밝힘으로 수학의 모든 사실을 만들 수 없다는 것이었다.

 

튜링은 단순한 3가지 종류의 기계 부품을 통해 괴델의 증명을 재구성한다. 이런 튜링의 아이디어는 현대 컴퓨터의 주요한 아이디어가 되었다.

테이프, 테이프에 읽고 쓰는 장치, 작동규칙표가 그것인데 이 개념들은 각각 메모리칩, 메모리 입출력 장치, 중앙처리장치(CPU)가 된다.

 

2-2.  400년

수학자들은 계속해서 수학의 모든 추론과정을 담는 논리추론의 패턴을 찾고자 한다. 이러한 노력이 400년 동안 이어오던 꿈이 튜링과 괴델에 의해 사라졌다. 이 과정에서 컴퓨터의 청사진이 우연찮게 드러나게 된것이다!

 

3. 그 도구의 실현

Boolean Logic와 Switch가 실현의 주 아이디어가 된다.

Boolean Logic가 Switch가 근본적으로 같은 것이 발견된다.

=> 즉 임의의 Boolean 식이 있으면 이에 해당하는 Switch 회로가 존재하는 것이다!

 

이 발견은 공략과 분석을 가능케 했고 체계적이고 최적화된 회로 구성을 만들 수 있게 하였다.

 

튜링기계의 실현방식 : abstraction hierarchy

- 속 내용을 감추며 차곡차곡 쌓는 방식이다.

- 단계별로 쌓아가며 더 크고 복잡한 물건을 만들게 해준다.

 

컴퓨터의 모든 부품은 디지털 논리회로로 구현가능하다!

 

4. 소프트웨어, 지혜로 짓는 세계

어떻게 소프트웨어를 잘 만들것인가?

이에 대한 대답을 2가지로 분리된다.

1) 일하는 방도를 어떻게 만들것인가 -> Algorithm

2) 어떤 방식으로 표현할 것인가 -> Language

 

Algorithm

- 컴퓨터가 따라할 문제풀이 법이다.

- 모든 알고리즘은 복잡도를 갖는다. 복잡도는 알고리즘 실행시 드는 시간,메모리와 같은 비용을 의미한다.

P class 문제

- 현실적인 비용으로 풀 수 있는 문제들을 의미한다.

- 이는 곧 다항 알고리즘이 발견된 문제들이다.

 

NP class

- 운에 맡겨야만 현실적인 비용으로 해결가능한 풀기 어려운 문제들이다.

- 암호기술(보안,암호화)에서 유용하게 쓰이기도 한다. 비트코인같은 기술도 np class의 예인 것으로 알고 있다.

 

양자알고리즘

- 전기스위치 대신에 원자 내부 양자 quantum을 이용해서 튜링의 컴퓨터를 구현할 수 있다.

- 중첩, 얽힘현상, 확률진폭을 이용한다.

 

5. 그 도구의 응용

1) 인간지능의 확장

- 기계가 대체가능한 일의 범위가 점점 증가하는 반면 인간만이 할 수 있는 일의 범위는 줄어든다.

- 이는 축복이다. 기계에게 위탁하지 못해 바빠 억눌려있던 인간 고유의 지능이 깨어나기 때문이다.

- 인간과 기계는 콤비가 되어 서로 확장되어갈 수 있다.

- 현대 물리학에서는 손으로 풀 수 있는 방정식이 5% 미만인 것으로 추정된다.

 

2) 인간본능의 확장

- 소통본능의 확장 : SNS

- 놀이본능 : 다체로운 게임들

- 세넌의 통신이론 정의

 

3) 인간현실의 확장

- 전화/이메일 등

 

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

 

14722번: 우유 도시

영학이는 딸기우유, 초코우유, 바나나우유를 좋아한다. 입맛이 매우 까다로운 영학이는 자신만의 우유를 마시는 규칙이 있다.  맨 처음에는 딸기우유를 한 팩 마신다. 딸기우유를 한 팩 마신 후

www.acmicpc.net

 

문제

영학이는 딸기우유, 초코우유, 바나나우유를 좋아한다.

입맛이 매우 까다로운 영학이는 자신만의 우유를 마시는 규칙이 있다. 

  1. 맨 처음에는 딸기우유를 한 팩 마신다.
  2. 딸기우유를 한 팩 마신 후에는 초코우유를 한 팩 마신다.
  3. 초코우유를 한 팩 마신 후에는 바나나우유를 한 팩 마신다.
  4. 바나나우유를 한 팩 마신 후에는 딸기우유를 한 팩 마신다. 

저번 축제에서 수많은 우유를 마셨지만 더욱 우유에 갈증을 느낀 영학이는 우유 여행을 떠났다.

맛있는 우유를 찾아 떠난 영학이는 수많은 우유 가게로 둘러 쌓인 어느 도시에 도착했다.

이 도시는 정사각형 형태의 2차원 격자 모양으로 남북으로 N개, 동서로 N개, 총 N*N개의 우유 가게들이 있다.

영학이는 도시의 서북쪽 끝 (1, 1)에서 출발해서 동남쪽 아래 (N, N)까지 까지 가면서 우유를 사 마신다. 

각각의 우유 가게는 딸기, 초코, 바나나 중 한 종류의 우유만을 취급한다.

각각의 우유 가게 앞에서, 영학이는 우유를 사 마시거나, 사 마시지 않는다.

So cooooool~한 영학이는 오직 동쪽 또는 남쪽으로만 움직이기 때문에 한 번 지나친 우유 가게에는 다시 가지 않는다.

영학이가 마실 수 있는 우유의 최대 개수를 구하여라.

입력

첫 번째 줄에는 우유 도시의 영역 크기 N이 주어진다. (1 ≤ N ≤ 1000)

두 번째 줄부터 N+1 번째 줄까지 우유 도시의 정보가 주어진다.

0은 딸기우유만을 파는 가게, 1은 초코우유만을 파는 가게, 2는 바나나우유만을 파는 가게를 뜻하며, 0, 1, 2 외의 정수는 주어지지 않는다.

출력

영학이가 마실 수 있는 우유의 최대 개수를 출력하시오.

 

접근방법 및 풀이

처음에는 다음과 같은 BFS 방식으로 접근했다.

#include <iostream>
#include <queue>
#include <algorithm>
#define VMAX 1000

using namespace std;

int n;
int b[VMAX][VMAX];
int result=0;
int w[VMAX][VMAX]={0}; // mem 배열

int dx[2] = {0,1};
int dy[2] = {1,0};

void bfs(){
    queue<pair<pair<int,int>,pair<int, int>>> q;

    if(b[0][0]==0) w[0][0]=1;
    q.push({{0,0},{w[0][0], w[0][0]}});

    while(!q.empty()){
        int x = q.front().first.first;
        int y = q.front().first.second;
        int c = q.front().second.first;
        int next = q.front().second.second;
        q.pop();
        if(x==n-1 && y==n-1){
            result = max(result, c);
        }
        for(int i=0; i<2; i++){
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(nx < n && ny < n && w[nx][ny] < c){
                if(next == b[nx][ny]){
                    next++;
                    c++;
                    if(next==3) next=0;
                }
                w[nx][ny] = c;
                q.push({{nx,ny}, {c, next}});
            }
        }
    }
    return ;
}

int main()
{
    cin >> n;
    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++){
            scanf("%d", &b[i][j]);
        }
    }
    bfs();
    
    cout << result;
    
    return 0;
}

바로 시간초과가 났다.

n의 최대값이 1000이라 당연한 결과였다.

바로 DP로 접근해야겠다는 판단이 들었다.

 

먼저 현재칸의 우유를 마실 수 있는지 여부를 판단하여 위쪽에서 내려오는 것과 왼쪽에서 오는 것 중 어느쪽이 이득인지 확인한다.

그 후 이득이 방향으로 우유사실 수 있는지 여부를 통해 mem을 채워나간다.

#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
#define VMAX 1000
#define INF 987654321

using namespace std;

int findnext(int next){
  next++;
  if(next==3) next=0;
  return next;
}

int main(){
  int n;
  int b[VMAX][VMAX];
  int mem[VMAX][VMAX];
  int v[VMAX][VMAX]={0};
  cin >> n;
  for(int i=0; i<n; i++){
    for(int j=0; j<n; j++){
      scanf("%d", &b[i][j]);
    }
  }
  if(b[0][0]==0){
    mem[0][0]=1;
    v[0][0]=1;
  }else{
    mem[0][0]=0;
    v[0][0]=0;
  }

  for(int i=1; i<n; i++){
    int next = v[0][i-1]; // 이번차례에 마실 수 있는 우유 종류 구하기

    if(next==b[0][i]){
      mem[0][i] = mem[0][i-1]+1;
      v[0][i]=findnext(next);
    }else{
      mem[0][i] = mem[0][i-1];
      v[0][i] = next;
    }
  }

  for(int i=1; i<n; i++){
    int next = v[i-1][0];// 이번차례에 마실 수 있는 우유 종류 구하기
        
    if(next==b[i][0]){
      mem[i][0] = mem[i-1][0]+1;
      v[i][0]=findnext(next);
    }else{
      mem[i][0] = mem[i-1][0];
      v[i][0]=next;
    }
  }

  for(int i=1; i<n; i++){
    for(int j=1; j<n; j++){
      // 이번차례에 마실 수 있는 우유 종류 구하기 - 각 위,왼쪽에 대해서
      int next_top = v[i-1][j];
      int next_left = v[i][j-1];

      int top, left;
      if(b[i][j] == next_top){
        top = mem[i-1][j] + 1;
      }else{
        top = mem[i-1][j];
      }
      if(b[i][j] == next_left){
        left = mem[i][j-1] + 1;
      }else{
        left = mem[i][j-1];
      }

      if(top > left){ // top 기준으로 구하기
        mem[i][j] = top;
        if(next_top==b[i][j]){
          v[i][j]=findnext(next_top);
        }else{
          v[i][j]=next_top;
        }
      }else{ // left 기준으로 구하기 어차피 우유 number를 고려한 상태이기 때문에 상관없다.
        mem[i][j] = left;
        if(next_left==b[i][j]){
          v[i][j]=findnext(next_left);
        }else{
          v[i][j]=next_left;
        }
      }

    }
  }
  cout << mem[n-1][n-1];
  
  return 0;
}

느낀점

- DP의 위력을 실감했다.

- 점화식을 좀더 깔끔하게 세워서 mem 배열하나로 접근해 다이렉트로 풀지는 못해서 아쉽다.

'알고리즘 문제풀이' 카테고리의 다른 글

[백준] 1799번 비숍 c++  (0) 2021.08.28
[백준] 16509번 장군 c++  (0) 2021.08.27
[백준] 14719번 빗물 c++  (0) 2021.08.26
[백준] 14716번 현수막 c++  (0) 2021.08.26
[백준] 2211번 네트워크 복구 c++  (0) 2021.08.24

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

 

14719번: 빗물

첫 번째 줄에는 2차원 세계의 세로 길이 H과 2차원 세계의 가로 길이 W가 주어진다. (1 ≤ H, W ≤ 500) 두 번째 줄에는 블록이 쌓인 높이를 의미하는 0이상 H이하의 정수가 2차원 세계의 맨 왼쪽 위치

www.acmicpc.net

문제

2차원 세계에 블록이 쌓여있다. 비가 오면 블록 사이에 빗물이 고인다.

비는 충분히 많이 온다. 고이는 빗물의 총량은 얼마일까?

입력

첫 번째 줄에는 2차원 세계의 세로 길이 H과 2차원 세계의 가로 길이 W가 주어진다. (1 ≤ H, W ≤ 500)

두 번째 줄에는 블록이 쌓인 높이를 의미하는 0이상 H이하의 정수가 2차원 세계의 맨 왼쪽 위치부터 차례대로 W개 주어진다.

따라서 블록 내부의 빈 공간이 생길 수 없다. 또 2차원 세계의 바닥은 항상 막혀있다고 가정하여도 좋다.

출력

2차원 세계에서는 한 칸의 용량은 1이다. 고이는 빗물의 총량을 출력하여라.

빗물이 전혀 고이지 않을 경우 0을 출력하여라.

 

문제접근 및 풀이

- 빗물이 고이는 위치를 어떻게 찾느냐가 관건이었다.

- 해당 칸을 기준으로 양 옆 사이드에 벽이 존재하면 빗물이 고인다는 것으로 판단했다.

- 전체 배열을 탐색하면서 각 줄에 대해서 몇칸의 빗물을 받을 수 있는지 확인했다.

- 변수 2개를 두었다.

find : 벽을 발견했는지 못했는지 확인한다.

count : 빗물이 고일 수 있는 칸 개수를 센다.

 

다음과 같은 방식으로 탐색을 진행하였다.

- 각 줄 탐색시 벽을 만나면 find가 true가 된다.

- find가 true인 상태에서 빈칸을 만나면 count가 1씩 증가한다.

- 다시 벽을 만나면 결과값에 지금까지 찾은 count를 더하고 count를 0으로 초기화시킨다.

 

#include <iostream>
#define VMAX 500

using namespace std;

int main()
{
    int result=0;
    int input[VMAX];
    int b[VMAX][VMAX]={0};
    int h,w; cin >> h >> w;
    for(int i=0; i<w; i++) cin >> input[i];
    
    for(int i=0; i<w; i++){
        for(int j=0; j<input[i]; j++){
            b[j][i]=1;
        }
    }
    
    for(int i=0; i<h; i++){
        int find = false;
        int count=0;
        for(int j=0; j<w; j++){
            if(find==true && b[i][j]==0) count++;
            if(find==true && b[i][j]==1){
                result+=count;
                count=0;
            }
            if(find==false && b[i][j]==1) find=true;;
        }
    }
    
    cout << result;
    
    return 0;
}

 

느낀점

다른 분들의 풀이를 몇개 찾아봤다. 다른 풀이들 보다 굉장히 간단한 방식으로 푼것 같아 뿌듯했다.

+ Recent posts