#include<iostream> usingnamespacestd; int ans = 100; intmap[10][10]; int type[5] = {5, 5, 5, 5, 5 }; // 1 2 3 4 5 종류 voidUpdate(int x, int y, int size, int status){ for (int i = x; i < x + size; ++i) { for (int j = y; j < y + size; ++j) { map[i][j] = status; } } } boolCheck(int x, int y, int size){ if (x + size > 10 || y + size > 10) returnfalse; // 범위 밖 for (int i = x; i < x + size; ++i) { for (int j = y; j < y + size; ++j) { if (map[i][j] == 0) returnfalse; } } returntrue; } voidDFS(int n, int cnt){ if (n == 100) { // 모든 경우를 탐색한 경우 (총 100개) if (ans > cnt) ans = cnt; return; } int x = n / 10; int y = n % 10; if (ans <= cnt) return; // BackTracking
if (map[x][y] == 1) { for (int i = 5; i > 0; --i) { // 크기 5부터 시작 if (type[i-1] > 0) { if (Check(x, y, i)) { type[i - 1]--; Update(x, y, i, 0); // 색종이 붙이기 DFS(n+1, cnt + 1); Update(x, y, i, 1); // 색종이 다시 때기 type[i - 1]++; } } } } else DFS(n + 1, cnt); } intmain(){ for (int i = 0; i < 10; ++i) { for (int j = 0; j < 10; ++j) { cin >> map[i][j]; } } DFS(0, 0); if (ans == 100) ans = -1; cout << ans << "\n"; return0; }
#include<iostream> #include<cstring> #include<cmath> usingnamespacestd; int N, M, D, ans; intmap[16][16]; bool check[16][16]; int archer[3] = { -1, -1, -1 }; intgetDistance(int a_x, int a_y, int b_x, int b_y){ returnabs(a_x - b_x) + abs(a_y - b_y); } voidGame(){ int step = 0, res = 0; memset(check, 0, sizeof(check)); while (true) { int attack_cnt = 0; for (int i = N - step - 1; i >= (N -step) - D && i >= 0; --i) { for (int k = attack_cnt; k < 3; ++k) { // 3명의 궁수 for (int j = 0; j < M; ++j) { if (D >= getDistance(i, j, N - step, archer[k]) && map[i][j] == 1 && !check[i][j]) { // 공격 개시 res++; check[i][j] = true; attack_cnt++; break; // 한 번만 공격 가능 } } } } step++; if (N - step - 1 < 0) break; } if (ans < res) ans = res; } voidSelectPosition(int n, int cnt){ if (cnt == 3) { Game(); return; } for (int i = n; i < M; ++i) { if (archer[cnt] != -1) continue; archer[cnt] = i; SelectPosition(i + 1, cnt + 1); archer[cnt] = -1; } } intmain(){ cin >> N >> M >> D; for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { cin >> map[i][j]; } } SelectPosition(0, 0); cout << ans << "\n"; return0; }
#include<iostream> #include<cstring> #include<queue> usingnamespacestd; int N, M, D, ans; intmap[16][16]; int check[16][16]; int archer[3] = { -1, -1, -1 }; int dx[3] = { 0, -1, 0 }; int dy[3] = { -1, 0, 1 }; voidGame(){ int step = 0, res = 0; // step은 1턴이 끝날 때 이동을 위함 memset(check, 0, sizeof(check)); while (true) { for (int i = 0; i < 3; ++i) { // 궁수 각 3명에 대하여 queue<pair<int, int> > q; q.push({ N - step, archer[i] }); // 궁수 현재 위치부터 시작 for(int j = 0; j < D; ++j) { // 최대 D까지만큼만 탐색 int len = q.size(); bool end_flag = false; // 적을 공격했을 때 종료 flag for (int k = 0; k < len; ++k) { int x = q.front().first; int y = q.front().second; q.pop(); for (int dir = 0; dir < 3; ++dir) { // 왼 위 오 순서로 탐색 시작 int d_x = x + dx[dir]; int d_y = y + dy[dir]; if (d_x > -1 && d_y > -1 && d_x < N - step && d_y < M) { // N - step은 궁수 위치 if (d_x < N - step - D) continue; // [N - step - D, N-step) 탐색 범위 if (map[d_x][d_y] == 1 && check[d_x][d_y] == 0) { // 처음 적을 공격한 경우 res++; check[d_x][d_y] = step + 1; // step을 옮겼을 때 중복 방지를 위함 end_flag = true; break; } elseif (map[d_x][d_y] == 1 && check[d_x][d_y] == step + 1) { // 동시에 적을 공격한 경우 end_flag = true; break; } else { // 적을 발견하지 못 한 경우 q.push({ d_x, d_y }); } } } if (end_flag) break; } if (end_flag) break; } } step++; if (N - step - 1 < 0) break; } if (ans < res) ans = res; } voidSelectPosition(int n, int cnt){ if (cnt == 3) { Game(); return; } for (int i = n; i < M; ++i) { if (archer[cnt] != -1) continue; archer[cnt] = i; SelectPosition(i + 1, cnt + 1); archer[cnt] = -1; } } intmain(){ cin >> N >> M >> D; for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { cin >> map[i][j]; } } SelectPosition(0, 0); cout << ans << "\n"; return0; }
Goal: (0, 0) 과 (0, 1)에 위치한 하나의 파이프를 (N, N)으로 옮기는 방법의 개수 불가능한 경우 0
파이프 옮기기
오른쪽, 아래, 오른쪽 아래 대각선 방향으로 옮길 수 있음 옮길 때 45도 회전 가능 벽에 닿으면 안됨
가로(수평) → 오른쪽 or 오른쪽 아래 대각선
세로(수직) → 아래 or 오른쪽 아래 대각선
오른쪽 아래 대각선 → 3가지 모두 가능
입력
N: 3~16
빈칸 0 벽은 1
// 예제
0 0 1 0
0 0 0 0
0 0 0 0
0 0 0 0
-> 0
맨 처음 파이프는 (0,0), (0,1) 즉 가로이기에 가능한 방향인
오른쪽, 오른쪽 아래 대각선으로 옮길 수 없다.
Solution
현재 type 정보와 위치를 알고 있어야 한다.
현재 type에 따라 옮길 수 있는 방향이 다르다.
현재 type에 따라 옮길 수 있는지 check를 해주어야 한다.
check
1 2 3 4 5 6 7 8 9 10 11 12 13
가로: 오른쪽 1칸 검사 세로: 아래 1칸 검사 대각선: 오른쪽 1칸, 아래 1칸, 오른쪽 아래 1칸 검사 // # 1. 가로 -> 가로 (자기 자신) -> 대각선 // # 2. 세로 -> 세로 (자기 자신) -> 대각선 // # 3. 대각선 -> 세로 -> 가로 -> 대각선
모두 자기 자신의 타입 그대로 옮길 수 있다.
대각선을 가기 위해서는 가로, 세로로 갈 수 있는 조건 역시 만족해야 한다.
위를 이용하여 탐색 코드를 구현한다.
1 2 3 4 5 6 7 8
void 탐색(현재 위치, 현재 타입) 도착했을 때 answer++후 리턴 for(3번) check할 위치(오른쪽, 아래, 오른쪽 아래) 경계확인 (벽이 아니고 자신과 같은 type)이거나(벽이 아니고 대각선) 탐색(옮길 위치, 가능한 타입) 모든 곳을 검사했을 때만 대각선 탐색
#include<cstdio> #define MAX 16 usingnamespacestd; int n, answer; intmap[MAX][MAX]; bool check[MAX][MAX]; // 오른쪽 1칸 검사, 아래 1칸 검사, 오른쪽 아래 1칸 검사 int dx[3] = { 0, 1, 1 }; int dy[3] = { 1, 0, 1 }; boolisBound(int r, int c){ if (r > -1 && c > -1 && r < n && c < n) returntrue; returnfalse; } // type 0 : 가로, 1 : 세로, 2 : 대각선 voiddfs(int r, int c, int type){ if (r == n - 1 && c == n - 1) { answer++; return; } // 현재 타입이 가로일 때 어차피 대각선 때문에 3칸 다 검사해야 한다. int x, y; bool flag = true; for (int i = 0; i < 3; ++i) { x = r + dx[i]; y = c + dy[i]; if (isBound(x, y)) { // 같은 type일 경우 or 대각선일 경우, i==2이면 대각선 경우가 한 번 더 구해지는 꼴이다. if ((map[x][y] == 0 && type == i || map[x][y] == 0 && type == 2) && i != 2) { dfs(x, y, i); } elseif (map[x][y] != 0) flag = false; } else flag = false; } if (flag) { dfs(x, y, 2); // 오른쪽 아래 대각선 } } intmain(){ scanf("%d", &n); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { scanf("%d", &map[i][j]); } } dfs(0, 1, 0); printf("%d\n", answer); return0; }
voidfindPrey(int row, int col){ queue<pair<int, int>> q, prey; q.push({ row, col }); memset(check, 0, sizeof(check)); check[row][col] = true; int part_time = 0; while (!q.empty()) { // 순회할 때마다 1초 증가 part_time++; int len = q.size(); for (int i = 0; i < len; ++i) { int r = q.front().first; int c = q.front().second; q.pop(); for (int j = 0; j < 4; ++j) { int x = r + dx[j]; int y = c + dy[j]; if (isBound(x, y) && baby.size >= map[x][y] && !check[x][y]) { if (baby.size > map[x][y] && map[x][y] > 0) { prey.push({ x, y }); } check[x][y] = true; q.push({ x, y }); } } } if (!prey.empty()) { selectPrey(prey); time += part_time; return; } } isEnd = true; }
#include<cstdio> #include<cstring> #include<queue> #define MAX 21 usingnamespacestd; int n, time; bool isEnd; intmap[MAX][MAX]; bool check[MAX][MAX]; int dx[4] = { -1, 0, 0, 1 }; int dy[4] = { 0, -1, 1, 0 }; structshark { int r, c, size, cnt= 0; }baby; boolisBound(int r, int c){ if (r > -1 && c > -1 && r < n && c < n) returntrue; returnfalse; } voidselectPrey(queue<pair<int, int>> &prey, queue<pair<int, int>> &q){ int prior_x = n, prior_y = n; while (!prey.empty()) { int x = prey.front().first; int y = prey.front().second; prey.pop(); if (x < prior_x) { prior_x = x; prior_y = y; } elseif (x == prior_x) { if (y < prior_y) { prior_y = y; } } } map[baby.r][baby.c] = 0; map[prior_x][prior_y] = 9; baby.r = prior_x; baby.c = prior_y; baby.cnt++; if (baby.cnt == baby.size) { baby.size++; baby.cnt = 0; } q.push({ prior_x, prior_y }); } voidfindPrey(int row, int col){ queue<pair<int, int>> q, prey; q.push({ row, col }); check[row][col] = true; int part_time = 0; while (!q.empty()) { // 순회할 때마다 1초 증가 part_time++; int len = q.size(); for (int i = 0; i < len; ++i) { int r = q.front().first; int c = q.front().second; q.pop(); for (int j = 0; j < 4; ++j) { int x = r + dx[j]; int y = c + dy[j]; if (isBound(x, y) && baby.size >= map[x][y] && !check[x][y]) { if (baby.size > map[x][y] && map[x][y] > 0) { prey.push({ x, y }); } check[x][y] = true; q.push({ x, y }); } } } if (!prey.empty()) { while (!q.empty()) q.pop(); selectPrey(prey, q); time = part_time; memset(check, 0, sizeof(check)); } } isEnd = true; } intmain(){ scanf("%d", &n); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { scanf("%d", &map[i][j]); if (map[i][j] == 9) { // 아기 상어 정보 baby.r = i; baby.c = j; baby.size = 2; } } } while (!isEnd) { findPrey(baby.r, baby.c); } printf("%d\n", time); return0; }
while (!q.empty()) q.pop(); 라인 때문에 시간초과가 걸린 것 같아 굳이 이럴 필요 없이 먹을 수 있는 물고기가 있다면 selectPrey() 를 호출하여 물고기를 선택하고 시간을 갱신해준 후 return하도록 하였다. 이러면 다시 findPrey() 를 호출하기에 업데이트 된 아기 상어 위치가 queue에 들어가고 작업을 수행한다. q를 비울 필요가 없어짐.
처음에 3차원 벡터로 해당 칸에 나무 정보를 저장하였지만 계절마다 작업을 수행할 때 상당히 비효율적이라 시간초과가 떴다.
→ 문제의 조건을 잘 보면 답이 보인다. (항상 느끼는 거지만 어떤 자료구조를 선택할지가 중요)
나이가 어린 나무부터 양분을 먹는다. (해당 칸에 있는 나무 정보를 순회할 때 어린 나무부터 접근해야 한다.)
나무 정보를 처음에 입력받을 때 한 칸에 하나씩만 받는다.
나무가 추가될 때 나이가 1인 나무가 추가된다.
나이가 클수록 먼저 죽음(나이만큼 양분을 먹기 때문이다.)
위 조건을 도합하면 deque 가 가장 적합하다. deque<int> tree[10][10] 나이가 어린 나무가 앞에 즉, 오름차순으로 정렬되어 있어야 하는데, 처음에 각 칸에 하나씩 입력 받고 추가될 때 앞부분에 나무를 추가해주면 되기 때문이다. 죽는건 앞에서부터 탐색을 하다가 죽는 나무가 생기면 그 뒤부터 이미 죽은 나무가 되기에 죽을 나무 개수만 카운트하고 뒤에서부터 개수만큼 pop하면 되기 때문이다.
#include<iostream> #include<algorithm> #include<vector> usingnamespacestd; int N, M, K, ans, diff; int land[10][10]; int A[10][10]; int dx[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; int dy[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; vector<vector<vector<int>>> v; voidInput(){ cin >> N >> M >> K; for (int i = 0; i < N; ++i) { v.resize(N); for (int j = 0; j < N; ++j) { v[i].resize(N); cin >> A[i][j]; land[i][j] = 5; } } for (int i = 0; i < M; ++i) { int r, c, age; cin >> r >> c >> age; v[r-1][c-1].push_back(age); } } voidTask(){ // 봄, 여름 for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { if (v[i][j].size() > 1) sort(v[i][j].begin(), v[i][j].end()); int sum = 0; for (int k = 0; k < v[i][j].size(); ++k) { if (v[i][j][k] == 0) continue; if (land[i][j] - v[i][j][k] < 0) { sum += v[i][j][k] / 2; v[i][j][k] = 0; // 죽은 표시 diff++; } else { land[i][j] -= v[i][j][k]; v[i][j][k]++; } } land[i][j] += sum; } } // 가을, 겨울 for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { for (int k = 0; k < v[i][j].size(); ++k) { if (v[i][j][k] == 0) continue; if (v[i][j][k] % 5 == 0) { for (int dir = 0; dir < 8; ++dir) { int r = i + dx[dir]; int c = j + dy[dir]; if (r <= -1 || c <= -1 || r >= N || c >= N) continue; v[r][c].push_back(1); } } } land[i][j] += A[i][j]; } } } intmain(){ Input(); while (K--) { Task(); } for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { ans += v[i][j].size(); } } ans -= diff; cout << ans << "\n"; return0; }
#include<iostream> #include<deque> usingnamespacestd; int N, M, K, ans; int land[10][10]; int A[10][10]; int dx[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; int dy[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; deque<int> tree[10][10]; voidInput(){ cin >> N >> M >> K; ans = M; for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { cin >> A[i][j]; land[i][j] = 5; } } for (int i = 0; i < M; ++i) { int r, c, age; cin >> r >> c >> age; tree[r - 1][c - 1].push_back(age); } } voidTask(){ // 봄, 여름 for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { if (tree[i][j].empty()) continue; int sum = 0, dead_num = 0; for (auto iter = tree[i][j].begin(); iter != tree[i][j].end(); ++iter) { if (land[i][j] - *iter < 0) { sum += (*iter) / 2; dead_num++; ans--; } else { land[i][j] -= (*iter); (*iter)++; } } for (int k = 0; k < dead_num; ++k) tree[i][j].pop_back(); land[i][j] += sum; } } // 가을, 겨울 for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { for (auto iter = tree[i][j].begin(); iter != tree[i][j].end(); ++iter) { if ((*iter) % 5 == 0) { for (int dir = 0; dir < 8; ++dir) { int r = i + dx[dir]; int c = j + dy[dir]; if (r <= -1 || c <= -1 || r >= N || c >= N) continue; tree[r][c].push_front(1); ans++; } } } land[i][j] += A[i][j]; } } } intmain(){ Input(); while (K--) { Task(); } cout << ans << "\n"; return0; }
N x N 크기의 배열을 전부 탐색하면서 check표시가 되어있지 않은 부분은 인구 이동이 가능한지 확인 작업이 수행된다.
작업 동안 누적 합과 연합에 포함된 나라 수를 계산해야 한다.
queue가 비어있을 때까지 다음을 수행한다.
현재 위치에서 4방향 탐색 → 범위체크, 탐색할 위치가 미탐색인지 확인
탐색 가능하면 누적 합, 나라 수 계산, 종료되지 않게 flag 갱신, queue에 넣어준다.
만약 나라 수가 1보다 크면 누적합과 사람 수를 따로 저장한다.
위 작업이 끝나면 이제 따로 저장한 누적합과 사람 수를 이용해 N x N 크기의 배열을 바꿔준다. (실질적 인구 이동)
만약 flag가 false 즉, 인구이동이 없다면 종료한다.
DFS
BFS보다 훨씬 빠르고 깔끔하며 명료하다. 처음에 BFS로 할 생각을 했던 것은 N x N 크기의 배열 값을 미리 바꾸면 인구 이동에 영향을 미치게 된다는 생각에 코드를 작성했었는데 생각해보니 한 번 탐색이 끝나면(BFS든 DFS든) check 표시 되어 있기에 영향을 주지 않고 한 번 탐색할 때 위치만 미리 벡터에 저장해주면 된다.
치킨 거리 ( 집: (r1, c1) 치킨집: (r2, c2) ) 집과 가장 가까운 치킨집 사이의 거리를 말한다.
Goal: 치킨집 중에서 최대 M개를 골랐을 때 도시의 치킨 거리 최솟값
Solution
치킨 집을 M개 고르는 모든 조합 구하기
해당 집을 고르면 배열에 넣고 아니면 넣지 않음
1 2 3 4 5 6 7 8 9 10 11
voidcombination(int idx){ if (select_idx == m) { BFS 이용하여 치킨 거리 구하기 return; } if (idx == total_chicken) return; combination(idx + 1); // 선택하지 않기 select[select_idx++] = idx; combination(idx + 1); // 선택하기 select_idx--; // 다른 경우의 수를 위해 인덱스 빼주기 (중요) }
중요라고 되어있는 부분을 작성하지 않으면 모든 경우의 수를 구할 수 없다. 경우의 수가 꼬여버림.
4개 중에 2개를 고른다고 하면
1 2 3 4 5 6 7 8
comb(0) comb(1) comb(2) comb(3) ... 이런식으로 호출이 이루어지기에 (3, 2) 경우가 먼저 완성된다. 완성되고 BFS이용하여 치킨 거리 구하고 return되면 select_idx를 빼준다. 그럼 2자리에 다른 경우가 넣어질 수 있다. (3, 1)
각 조합에 대한 최소한의 치킨 거리 구하기
BFS를 이용하여 한 칸씩 갈 때 1초 증가
그러다 1을 만나면 해당 초를 더 해준다. BFS를 이용하면 동시에 1을 만나기에 time만 더해주어서는 안된다. 동시에 발견한 집의 수만큼 time을 더해주어야 한다.
voidbfs(){ int time = 0, house = 0, dist = 0; memset(check, 0, sizeof(check)); queue<pair<int, int>> q; for (int i = 0; i < select_idx; ++i) { q.push({ chicken[select[i]].r, chicken[select[i]].c }); while (!q.empty()) { time++; int cnt = 0; int len = q.size(); for (int i = 0; i < len; ++i) { int r = q.front().first; int c = q.front().second; q.pop(); check[r][c] = true; for (int i = 0; i < 4; ++i) { int x = r + dx[i]; int y = c + dy[i]; if (isBound(x, y) && !check[x][y]) { check[x][y] = true; if (city[x][y] == 1) { house++; cnt++; } q.push({ x, y }); } } } dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기 } }
이 중 가장 최소인 치킨 거리 구하기
1 2 3 4 5
dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기 if (total_house == house) { answer = answer > dist ? dist : answer; return; }
#include<cstdio> #include<cstring> #include<queue> #define MAX 51 usingnamespacestd; int n, m, total_chicken, total_house, index, answer; int city[MAX][MAX]; bool check[MAX][MAX]; int select[13]; int dx[4] = { -1, 1, 0, 0 }; int dy[4] = { 0, 0, -1, 1 }; structINFO { int r, c; }chicken[13]; boolisBound(int r, int c){ if (r > -1 && c > -1 && r < n && c < n) returntrue; returnfalse; } voidbfs(){ int time = 0, house = 0, dist = 0; memset(check, 0, sizeof(check)); queue<pair<int, int>> q; for (int i = 0; i < index; ++i) { q.push({ chicken[select[i]].r, chicken[select[i]].c }); // 선택한 치킨집 queue에 저장 } while (!q.empty()) { time++; int cnt = 0; int len = q.size(); for (int i = 0; i < len; ++i) { int r = q.front().first; int c = q.front().second; q.pop(); check[r][c] = true; for (int i = 0; i < 4; ++i) { int x = r + dx[i]; int y = c + dy[i]; if (isBound(x, y) && !check[x][y]) { check[x][y] = true; if (city[x][y] == 1) { house++; cnt++; } q.push({ x, y }); } } } dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기 if (total_house == house) { answer = answer > dist ? dist : answer; return; } } } voidcombination(int idx){ // M개를 선택하는 모든 경우의 수 구하기 if (index == m) { bfs(); return; } if (idx == total_chicken) return; combination(idx + 1); select[index++] = idx; combination(idx + 1); index--; } intmain(){ scanf("%d %d", &n, &m); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { scanf("%d", &city[i][j]); if (city[i][j] == 2) { // 치킨집 정보 저장 chicken[total_chicken].r = i; chicken[total_chicken++].c = j; } elseif (city[i][j] == 1) total_house++; } } answer = 1e9; combination(0); printf("%d\n", answer); return0; }
#include<cstdio> #include<cstring> #include<queue> #define MAX 51 usingnamespacestd; int n, m, total_chicken, total_house, select_idx, answer; int city[MAX][MAX]; bool check[MAX][MAX]; int select[13]; int dx[4] = { -1, 1, 0, 0 }; int dy[4] = { 0, 0, -1, 1 }; structINFO { int r, c; }chicken[13]; boolisBound(int r, int c){ if (r > -1 && c > -1 && r < n && c < n) returntrue; returnfalse; } voidbfs(){ int time = 0, house = 0, dist = 0; memset(check, 0, sizeof(check)); queue<pair<int, int>> q; for (int i = 0; i < select_idx; ++i) { q.push({ chicken[select[i]].r, chicken[select[i]].c }); // 선택한 치킨집 queue에 저장 } while (!q.empty()) { time++; int cnt = 0; int len = q.size(); for (int i = 0; i < len; ++i) { int r = q.front().first; int c = q.front().second; q.pop(); check[r][c] = true; for (int i = 0; i < 4; ++i) { int x = r + dx[i]; int y = c + dy[i]; if (isBound(x, y) && !check[x][y]) { check[x][y] = true; if (city[x][y] == 1) { house++; cnt++; } q.push({ x, y }); } } } dist += time * cnt; // 해당 초에 만난 집의 수 만큼 이동거리 더해주기 if (total_house == house) { answer = answer > dist ? dist : answer; return; } } } voidcombination(int idx){ // M개를 선택하는 모든 경우의 수 구하기 if (select_idx == m) { bfs(); return; } if (idx == total_chicken) return; combination(idx + 1); select[select_idx++] = idx; combination(idx + 1); select_idx--; } intmain(){ scanf("%d %d", &n, &m); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { scanf("%d", &city[i][j]); if (city[i][j] == 2) { // 치킨집 정보 저장 chicken[total_chicken].r = i; chicken[total_chicken++].c = j; } elseif (city[i][j] == 1) total_house++; } } answer = 1e9; combination(0); printf("%d\n", answer); return0; }
시작 방향 0: x 좌표 증가 → 방향 1: y 좌표 감소 ↑ 방향 2: x 좌표 감소 ← 방향 3: y 좌표 증가 ↓ 방향
세대
0세대: 길이가 1인 선분
1세대: 0세대 드래곤 커브 끝 점을 기준으로 시계 방향 90도 회전시켜 0세대 끝 점에 붙인 것
2세대: 1세대를 이용하여 1세대를 만든 것처럼 만든다.
N세대: N-1세대 커브를 끝 점 기준으로 90도 시계 방향 회전시킨 것을 붙인 것
입력
드래곤 커브 개수 N (~20) x, y (드래곤 커브 시작 점) ~100 d (시작 방향) g (세대) ~10
드래곤 커브는 서로 겹칠 수 있다.
Goal: 만들어진 드래곤 커브에서 정사각형 4개의 꼭짓점이 모두 드래곤 커브의 일부인 것의 개수를 구하기 (모양이 정사각형이 아니어도 4개의 꼭짓점만 만족하면 된다.)
Solution
끝점을 기준으로 시계방향 90도를 했을 때 각 방향의 이동은 다음과 같다.
0 → 1
1 → 2
2 → 3
3 → 0
아래 그림 참고.
드래곤 커브를 그려주는 건 check 배열로 수행한다. (겹쳐도 되니까 초기화할 필요 없다.)
방향만 배열에 저장해주면 된다.
끝점에서 이동을 수행하니 위치 좌표는 끝점만 알면된다.
1 2 3 4 5
// input : 3 3 0 1 (3, 3) : 처음 시작 위치 (4, 3) : 0세대 // 위치 좌표: (4, 3) 방향 0 (4, 2) : 1세대 // 위치 좌표: (4, 2) 방향 0, 1 (3, 2), (3, 1) : 2세대 // 위치 좌표: (3, 2) -> (3,1) 방향 0, 1, 2, 1
2세대 설명: 1세대에서 방향이 [0, 1]로 저장되어 있다. 끝점부터 시작하기에 방향 1이 시계방향으로 90도 회전하면 방향 2가 된다. [0, 1, 2] 이후 0이 시계방향으로 90도 회전하면 방향 1이된다. [0, 1, 2, 1]
드래곤 커브 위의 규칙대로 그리기
0세대 까지 그려놓고 (0세대가 아니라면) 1세대부터 그린다.
direction 배열에 위의 [0, 1, 2, 1]과 같은 값이 들어간다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
int clockwise[4] = { 1, 2, 3, 0 }; voiddraw(int x, int y, int d, int g){ int idx = 0; check[y][x] = true; direction[idx++] = d; while(g--) { int len = idx; for (int i = len-1; i >= 0; --i) { d = clockwise[direction[i]]; x += dx[d]; y += dy[d]; if (x > -1 && y > -1 && x < MAX && y < MAX) { check[y][x] = true; } direction[idx++] = d; } } }
4개의 꼭짓점 확인
배열은 최대 100 x 100의 크기를 가지기에 0~99까지 check 값이 존재할 수 있다. (사실상 98까지만 확인하면 된다.)
1 1 (0, 0)을 기준으로 오른쪽, 아래, 오른쪽 아래 대각선만 확인하면 된다.
1 1
입력값을 보고 사다리 정보를 어떤 식으로 저장할건지가 첫 스타트이자 포인트다. 이렇게 data가 보여야 조합도 어떤식으로 구성할지 생각나기 때문이다.
작업은 2개로 나뉜다.
조합 구하기
사다리 타기
조합 구하기
조합을 구하기 전에 입력값이 어떻게 들어오나 확인해보자.
a b가 입력되면 a행에 b열 사다리와 b+1열 사다리가 연결된다. 이를 array[a][b] = 1(사다리 있음)으로 표시하면 b+1로 갈 수 있다는 뜻이다. 반대로 b+1지점에서 array[a][b]값이 1인걸 확인하면 b로 갈 수 있다는 뜻이다.
이를 활용하여 조합을 구해보자.
모든 행의 1열부터 N-1열까지 탐색해야 한다. 단, 현재 위치뿐만 아니라 자신의 왼쪽, 오른쪽도 확인해야 한다. (연속으로 설치하지 못하기 때문)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1행 1열 선택 시 다음 가능한 경우 (N에 표시하는 것은 의미 X - 입력값 생각) 1행 - 1(x), 2(x), 3 ... N-1 2행 - 1, 2, 3 ... N-1 ... H행 - 1, 2, 3 ... N-1
for (int i = idx; i <= H; ++i) { for (int j = 1; j < N; ++j) { if (visit[i][j]) continue; // 현재 확인 if (j > 1 && visit[i][j - 1]) continue; // 왼쪽 확인 if (visit[i][j + 1]) continue; // 오른쪽 확인 visit[i][j] = true; // 선택 표시 selectAll(i, cnt + 1); // 다음 선택 visit[i][j] = false; } }
#include<iostream> usingnamespacestd; int N, M, H, ans; bool visit[31][11]; voidInput(){ ans = 4; cin >> N >> M >> H; for (int i = 0; i < M; ++i) { int a, b; cin >> a >> b; visit[a][b] = true; // a행 b - b+1 사다리 } } boolCheck(){ for (int j = 1; j <= N; ++j) { int current_num = j; for (int i = 1; i <= H; ++i) { if (visit[i][current_num]) { // 오른쪽 사다리로 이동 current_num++; } elseif (current_num > 1 && visit[i][current_num -1]) { // 왼쪽 사다리로 이동 current_num--; } } if (current_num != j) returnfalse; } returntrue; } voidselectAll(int idx, int cnt){ if (cnt > ans) return; if (cnt == 4) { return; } if (Check()) { if (ans > cnt) ans = cnt; return; } for (int i = idx; i <= H; ++i) { for (int j = 1; j < N; ++j) { // 5번 사다리는 확인할 필요 없다. (입력값 생각) if (visit[i][j]) continue; if (j > 1 && visit[i][j - 1]) continue; if (visit[i][j + 1]) continue; visit[i][j] = true; selectAll(i, cnt + 1); visit[i][j] = false; } } } voidSolve(){ Input(); selectAll(1, 0); if (ans == 4) ans = -1; cout << ans << "\n"; } intmain(){ Solve(); return0; }