레이블이 BFS인 게시물을 표시합니다. 모든 게시물 표시
레이블이 BFS인 게시물을 표시합니다. 모든 게시물 표시

2016년 10월 31일 월요일

2146 - 다리 만들기

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

각 대륙마다 번호를 붙여서 동기적으로 대륙을 넓혀간다. 넓어가는 도중에 다른 대륙과 만난다면, 그 시점에 각 대륙이 넓혀진 거리만큼이 (부딪혔으므로) 연결된 거리이고, 그 중에 최소를 구한다.

동기적으로 넓혀가는 이유는 대륙 하나를 잡고 나머지와 비교하면 아래와 같은 경우가 생긴다.
대륙간의 거리를 모두 비교하면 \(O(N^2)\)이다. 물론 N이 최대 100까지이므로 모두 비교해도 문제될 건 없다.

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

int n, a[100][100];

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

bool visit[100][100];

bool giveId(int y, int x, const int id){
    if(y < 0 || y >= n || x < 0 || x >= n) return 0;
    
    if(visit[y][x] || a[y][x] == 0) return 0;
    visit[y][x] = true;
    
    a[y][x] = id;
    for(int i=0; i<4; ++i){
        giveId(y+dy[i], x+dx[i], id);
    }
    return 1;
}

struct land {
    int y, x, color;
};

int Distance[100][100];

int main(){
    scanf("%d ", &n);
    for(int i=0; i<n; ++i){
        for(int j=0; j<n; ++j){
            scanf("%d", &a[i][j]);
            Distance[i][j] = a[i][j] ? 0 : INF;
        }
    }
    
    int color = 1;
    for(int i=0; i<n; ++i){
        for(int j=0; j<n; ++j){
            color += giveId(i, j, color);
        }
    }
    
    queue<land> q;
    for(int i=0; i<n; ++i){
        for(int j=0; j<n; ++j){
            if(a[i][j] != 0) continue;
            
            for(int d=0; d<4; ++d){
                int ny=i+dy[d], nx=j+dx[d];
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue;
                if(0 != a[ny][nx]){
                    Distance[ny][nx] = min(Distance[ny][nx], 1);
                    q.push({i, j, a[ny][nx]});
                    break;
                }
            }
        }
    }
    
    int answer = 0;
    int dist = 0;
    while(!q.empty()){
        ++dist;
        int qs = q.size();
        while(qs--){
            int y = q.front().y;
            int x = q.front().x;
            int c = q.front().color;
            q.pop();
            
            if(a[y][x]) continue;
            a[y][x] = c;
            Distance[y][x] = dist;
            
            for(int d=0; d<4; ++d){
                int ny=y+dy[d], nx=x+dx[d];
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue;
                
                if(a[ny][nx] == 0){
                    q.push({ny, nx, c});
                } else if(a[ny][nx] != c){
                    int expected = dist + Distance[ny][nx];
                    if(answer) answer = min(answer, expected);
                    else answer = expected;
                }
            }
        }
        if(answer > 0) break;
    }
    
    printf("%d", answer);
    
    return 0;
}

2016년 4월 23일 토요일

2848 - 고창어

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

그냥 쭉 쓰다보니 코드가 너무 길어졌다.
새로 정의한 사전순 문자열로부터 문자의 순서를 재정의한다.
예제 같은 경우는 l->u, l->k, u->k, k->a 이렇게 4개

1. 올바른 순서가 없는 경우
 : 모순이 존재한다는 것. 싸이클이 있다는 것이다.
 : 플로이드 돌린 후에 다시 자신으로 돌아오는 경로가 있으면 싸이클임.

2. 가능한 순서가 여러개인 경우
 : 사전의 첫 글자가 여러개이거나, (첫 글자로부터) k번째 숫자에 올 수 있는 수가 여러개인 경우.

위에 적은 것만 구현했는데 코드가 1.6KB나 된다. 소스 정말 더럽다.

2016년 3월 20일 일요일

10472 - 십자뒤집기

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

3x3칸 크기의 보드를 1자로 펼치면 000 000 000 의 형태가 나온다. 각 칸의 상태를 */. 을 0/1로 표현하면 $2^9$ 가지 상태가 있다.

각 칸의 인접한 동서남북과 자신을 표현하면 아래와 같다.

int click[9]={
    416, //110 100 000
    464, //111 010 000
    200, //011 001 000
    308, //100 110 100
    186, //010 111 010
     89, //001 011 001
     38, //000 100 110
     23, //000 010 111
     11, //000 001 011
};

그리고 이 값을 XOR하면 그 칸을 선택하여 십자로 뒤집은 결과가 된다. 예를 들어, 101 000 010 의 가장 왼쪽 위칸을 십자로 뒤집으면 010 010 010 인데, 이건 101 000 010 XOR 111 010 000 이다.

너비 우선 탐색을 사용하여 000 000 000 부터 0~8번째 칸을 하나씩 뒤집어보면서 주어진 보드의 상태와 같아질 때 뒤집은 횟수를 출력하면 된다.

2016년 3월 10일 목요일

1175 - 배달

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

2차원 평면에서 S부터 2개의 C블럭을 모두 도달하는 최단 거리 BFS인데 조건이 있다.

같은 방향으로 2번 이상 이동할 수가 없다.

원래 BFS를 할때 visit[N][M] 처럼 했다면, "어디쪽에서 왔는가"도 추가해야한다.

그리고 2개의 C블럭을 모두 도달해야 한다. 이것을 C0, C1 으로 부른다면 어떤 지점까지 움직였을 때, 문제를 얼만큼 해결했는 가를 4가지 상태로 나타낼 수 있다.

1. C0, C1 을 모두 못 찾았다.
2. C0 을 찾았다
3. C1 을 찾았다.
4. C0, C1 을 모두 찾았다.

(C0를 찾았다, C1를 찾았다) 로 표현한다면 00, 10, 01, 11 로 나타낼 수 있고, 이를 2진수로 본다면 10진수 0, 2, 1, 3 이다.

4번의 상태가 나올때까지, 같은 방향으로 진행하지 않으면서, 이전에 왔던 위치에 이전과 똑같은 움직임으로 다시 방문하지 않고 (visit[y][x][previousDirect]), 그 때의 '얼만큼 찾았는 가'의 상태마저 같지 않은 경우만 탐색하면 된다.

2016년 3월 2일 수요일

1238 - 파티

처음에는 플로이드 와샬로 접근해봤다. 아니나다를까 N=1,000 이라서 $O(N^3)$ 은 시간초과였다.

처음으로 다익스트라를 구현해봤다.
잘 동작하는지는 모르겠고 흉내만 내는 것 같다.
#define INF 987654321

typedef pair<int,int> ii;

int dijkstra(vector<ii> adj[], int n, int s, int e){
    vector<int> cost(n, INF); /* s 로부터 i까지의 최소 비용 */
    priority_queue<ii /*(-weight, next)*/> q;
    q.push({0, s});
    cost[s] = 0;
    while(!q.empty()){
        int pos = q.top().second;
        int dist = -q.top().first;
        q.pop();
        
        for(int i=0; i<adj[pos].size(); ++i){
            ii& next = adj[pos][i]; /* (next, weight) */
            if(dist + next.second >= cost[next.first]) continue;
            q.push({-(dist+next.second), next.first});
            cost[next.first] = dist+next.second;
        }
    }
    return cost[e];
}
예제가 잘 나오길래 제출해봤는데 정답이 잘 나왔다. 시간이 좀 걸리는걸 보니 N이 좀 커지면 시간초과가 나올 듯 하다.

1963 - 소수 경로

에라토스테네스의 체로 소수를 걸러내면서 4자리의 소수 1001 ~ 9973를 모두 저장했다.

소수 X 에서 1자리의 변화로 가능한 수를 "인접리스트"처럼 만들었다.
예를 들면, 1021에서 이동 가능한 다음 소수는 1031, 1051, 1061, 1091, 1321, 1621, 1721, 4021, 5021 이다.

이게 가능한 이유는

1. 소수에서 출발하여 소수로 끝난다.
2. 중간 과정에서도 항상 4자리 소수임이 유지된다.

이기 때문이다.

그리고 이렇게 만들어진 인접리스트로 BFS를 돌렸다.

처음에는 "특정 소수 S 에서 다음 소수 T 까지의 최소 거리"를 누적하면서 DP를 하려 했다.

vector<int> adj[9999]; /* 이 소수로부터 1의 변화로 갈 수 있는 인접 소수 */
 
int S, T; /* S에서 T로 만들어야 함 */
int dp[9999]; /* memset(-1, .. */
 
int f(int cur){
    if(cur == T) return 0;
     
    int& r = dp[cur];
    if(r != -1) return r;
    r = INF;
    for(int i=0; i < adj[cur].size(); ++i){
        r = min(r, 1+f(adj[cur][i]));
    }
    return r;
}

왜냐하면 시작 소수를 S, 목적지를 T, 중간에 거친 임의의 소수를 K 라고 하면 K->T의 최소와 S->K 의 최소가 곧 S->T 가 되는 최소 거리라고 생각했기 때문이다.

안되는 이유를 못 찾아서 결국 BFS로 했는데, 8179 1733 와 1733 8179 의 케이스에서 서로 다르게 나오는 데, 나중에 자세히 디버깅해봐야겠다.

참고로 시작점이 소수인 이상, 문제에서 말하는 불가능한 경우는 존재하지 않는다.

BFS로 해결하는 소스는 아래와 같다.

2014년 10월 6일 월요일

MFC 미로 게임

Maze Generator

매번 만들어야지 생각만 하다가, 드디어 해봤다.
오랜만에 MFC를 잡아서인지 몇 시간 걸렸다.
이전에 선배 도와주면서 만들어본 "DFS와 BFS를 이용한 최단경로" 를 적용해서 [Find Path] 버튼을 추가할 예정.

역시 알고리즘은 이렇게 써먹으면 기분 좋음

한가지 재미있는 사실이라면, srand(time(NULL)) 같이 time함수를 잘 쓰지 않는 편이라 (온라인 저지 탓인ㄱ..) rand함수를 잘 사용하지 않았다.
그럼 어떻게 하느냐
srand((int)&val)); 과 같이 특정 변수의 주소를 정수로 변환해버린다.
그런데 이게 지역변수라 매번 할당을 다르게 할 줄 알았더니, Generate 버튼을 아무리 눌러도 항상 같은 미로를 만들었다. 주소값이 같은건가..!!
덕분에 좋은 난수 생성 알고리즘을 알았는데, 메르센 트위스터(MT) 고안자가 만든 WELL512 를 사용해봤다. 자세한 내용은 구글링 + 위키 참조


10-06 15:48 update. v1.2


- 셀 크기 설정 추가 (가로X세로에 대해 자동 계산)
- 정답 출력(Find Path) 추가
- 미로만 다시 그리기(Reset) 추가

여담:
- 셀 크기가 너무 작거나 크면 미로를 만들지 않도록 했는 데 먹히질 않는다 (!!)
- 셀 크기가 너무 작으면 튕긴다. 주의..
- BFS 과정 출력 버튼도 있었는데 너무 더러워서 제거 (....)


10-06 18:55 update. v1.3

- 랜덤하지 못하게 편향된 경로 결과 수정
- 셀 크기를 변경하고 Find Path를 누르면 잘못되던 버그 수정


10-06 20:37 update. v1.4.0

- 버튼 활성화/비활성화 추가
- 최소 셀 크기 제한
- 버튼 UI를 왼쪽으로 이동
- 숫자 입력 후 엔터 누르면 자동 Generate 반복문이 맞물려서 런타임 에러가 남. 일단 삭제
- 창 크기 및 출력 크기 변경 가능


10-10 20:02 update. v1.4.1

- 인쇄 기능 추가 (세로 모드로 출력)
- 왼쪽 상단에 미로의 크기 정보 표시
- 미로 난이도 상향 (...?)
- 길 찾기 결과 출력에 옵션 추가. (탈출로 / BFS 결과)
- 시작점과 출발점을 변경함 (윗쪽 중앙에서 아랫쪽 중앙으로 탈출)
- 그 외 기타 예외 처리.

Attached File
Maze_Generator.v1.4.1.exe

게시글 목록