[문제 링크]

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

 

16235번: 나무 재테크

부동산 투자로 억대의 돈을 번 상도는 최근 N×N 크기의 땅을 구매했다. 상도는 손쉬운 땅 관리를 위해 땅을 1×1 크기의 칸으로 나누어 놓았다. 각각의 칸은 (r, c)로 나타내며, r은 가장 위에서부터

www.acmicpc.net

 

[문제 풀이]

전형적인 삼성 시뮬레이션 기출문제이다.

문제에서 봄, 여름, 가을, 겨울에 하는 일을 정의해놨기 때문에 본인은 4개의 함수를 구현했다.

함수를 보기 전에 선언한 변수를 살펴보겠다.

MAP은 해당 자리에 있는 양분의 양이다. 양분은 처음에 모든 자리에 5가 있다.

eat은 겨울에 해당 자리에 추가되는 양분의 양이다.

tree는 해당 자리에 있는 나무들의 나이를 담는 배열이다. 여러 개의 나무가 같은 자리에 있을 수 있기 때문에 덱 배열을 사용하였다.

여기서 덱을 사용한 이유는 나무를 쉽게 푸시하고 팝 하기 위해서이다.

dead_tree는 해당 자리에서 죽은 나무이다. 마찬가지로 해당 자리에 죽은 나무가 여러 개 있을 수 있기 때문에 벡터 배열로 선언하였다.

그리고 가을에서 인접한 8자리에 번식하는 것을 구현하기 위해 dx, dy 배열을 사용하였다.

이 문제에서 가장 애먹은 부분은 봄 부분이었다.

봄의 조건에서 하나의 칸에 여러 개의 나무가 있다면, 나이가 어린 나무부터 양분을 먹는다고 되어 있다.

이 조건 때문에 tree 배열을 덱으로 선언하였다. 덱을 사용하면 가장 어린 나무를 바로 뽑을 수 있기 때문이다.

본인은 모든 자리를 탐색하면서 해당 자리에 나무의 크기만큼 반복문을 수행했다.

여기서 미리 해당 자리의 나무 개수를 구하고 나무 개수만큼 반복문을 돌린 이유는 반복문 안에서 푸시와 팝이 이루어지는데 미리 정해준 나무 개수만큼 돌리지 않으면 우리가 원하는 대로 결과가 나오지 않기 때문이다.

예를 들어 (0,0) 자리에 나이가 1인 나무가 3개 있고 양분은 7이라고 가정해보자.

여기서는 당연히 나무의 개수인 3번만큼만 반복문이 돌아야 한다.

그렇지 않다면 나이가 1이었던 나무들은 해당 자리에 양분이 충분하지 않을 때까지 나이를 먹을 것이다.

마지막 조건을 보면 땅에 양분이 부족해 자신의 나이만큼 양분을 먹을 수 없는 나무는 즉시 죽는다고 하였다.

이 말이 처음에는 조금 헷갈렸는데 말을 바꿔보면 현재 나무의 나이보다 해당 자리의 양분이 더 작다면 나무는 죽는 것이다.

이때 죽은 나무는 dead_tree 배열에 넣어줬다.

봄 부분만 해결하면 나머지 부분은 어렵지 않게 구현할 수 있다.

여름에서는 해당 자리에 죽은 나무가 있다면 그 죽은 나무의 나이를 2로 나눈 값을 해당 자리의 양분에다가 추가하면 된다.

가을에서는 나무의 나이가 5의 배수이면 인접한 8칸에 나이가 1인 나무를 추가한다.

여기서 tree 배열을 덱으로 선언한 이유가 나온다.

나이가 1인 나무라는 것은 결국 가장 어린 나무를 뜻한다. 그렇기 때문에 나무가 번식을 한다면 해당하는 자리 맨 앞에 1을 푸시해준다.

겨울은 가장 간단하다.

해당하는 자리에 처음에 입력받았던 양분을 추가해주면 된다.

이 작업을 K번 반복했을 때 남아있는 나무의 수를 구하는 문제이다.

K번 반복하고 난 후에 모든 자리를 탐색하면서 해당하는 자리의 사이즈를 더해주었다.

[소스 코드]

#include <bits/stdc++.h>
using namespace std;
int n, m, K;
int MAP[11][11];
int eat[11][11];
deque<int> tree[11][11];
vector<int> dead_tree[11][11];
int dx[8] = { -1,-1,-1,0,0,1,1,1 };
int dy[8] = { -1,0,1,-1,1,-1,0,1 };
void spring() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			int size = tree[i][j].size();
			while (size--) {
				int age = tree[i][j].front();
				tree[i][j].pop_front();
				if (MAP[i][j] < age) {
					dead_tree[i][j].push_back(age);
					continue;
				}
				MAP[i][j] -= age;
				tree[i][j].push_back(age + 1);
			}
		}
	}
}

void summer() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			int size = dead_tree[i][j].size();
			while (size--) {
				int age = dead_tree[i][j].back();
				dead_tree[i][j].pop_back();
				MAP[i][j] += (age / 2);

			}
		}
	}
}

void fall() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			for (int k = 0; k < tree[i][j].size(); k++) {
				int age = tree[i][j][k];
				if (age % 5 == 0) {
					for (int dir = 0; dir < 8; dir++) {
						int nx = i + dx[dir];
						int ny = j + dy[dir];
						if (nx < 1 || ny < 1 || nx > n || ny > n) continue;
						tree[nx][ny].push_front(1);
					}
				}
			}
		}
	}
}

void winter() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			MAP[i][j] += eat[i][j];
		}
	}
}

void count_tree() {
	int sum = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			sum += tree[i][j].size();
		}
	}
	cout << sum;
}
void INPUT() {
	cin >> n >> m >> K;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			MAP[i][j] = 5;
			cin >> eat[i][j];
		}
	}

	for (int i = 0; i < m; i++) {
		int x, y, z;
		cin >> x >> y >> z;
		tree[x][y].push_back(z);
	}
}

void solve() {
	while (K--) {
		spring();
		summer();
		fall();
		winter();
	}
	count_tree();
}
void solution() {
	INPUT();
	solve();
}
int main() {
	cin.tie(0);
	ios::sync_with_stdio(0);
	solution();
	return 0;
}

 

'알고리즘 > BOJ' 카테고리의 다른 글

백준 BOJ 17779 C++ 게리맨더링 2  (0) 2020.04.21
백준 BOJ 14891 C++ 톱니바퀴  (0) 2020.04.19
백준 BOJ 17822 C++ 원판 돌리기  (0) 2020.04.17

[문제 링크]

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

 

17779번: 게리맨더링 2

재현시의 시장 구재현은 지난 몇 년간 게리맨더링을 통해서 자신의 당에게 유리하게 선거구를 획정했다. 견제할 권력이 없어진 구재현은 권력을 매우 부당하게 행사했고, 심지어는 시의 이름도 재현시로 변경했다. 이번 선거에서는 최대한 공평하게 선거구를 획정하려고 한다. 재현시는 크기가 N×N인 격자로 나타낼 수 있다. 격자의 각 칸은 구역을 의미하고, r행 c열에 있는 구역은 (r, c)로 나타낼 수 있다. 구역을 다섯 개의 선거구로 나눠야 하고, 각 구역은 다

www.acmicpc.net

[문제 풀이]

삼성 시뮬레이션 문제이다.

이 문제는 처음에 이해하는데만 20분 이상 걸린 것 같다. 문제 설명이 부실한 느낌?을 많이 받았다.

이 문제에서는 조건들이 굉장히 많이 나오는데 결국 중요한 것은 N X N인 격자 내에서 5개의 선거구를 만들 수 있는 조건을 충족하면서 각 선거구 인구의 최댓값에서 최솟값을 뺀 값이 최솟값이 되어야 한다.

여기서 중요한 것은 5개의 선거구를 만들 수 있는 조건이다.

문제 조건 중 일부를 보면 경계선을 만들 수 있는 조건이 나와 있다. 이 경계선과 경계선 안에 포함되어 있는 곳이 5번 선거구가 된다. 그렇다면 이 5번 선거구를 어떻게 구현할 것인지 생각해봐야 한다.

본인은 입력으로 주어지는 선거구의 인구를 MAP이라는 2차원 배열에 저장하였다. 그리고 선거구를 나눠주기 위해 TEMP라는 2차원 배열을 사용하였다.

기본적인 흐름은 완전 탐색이다. 완전 탐색이기 때문에 i와 j가 0부터 n-1까지 탐색한다. 그리고 5개의 선거구를 만들 수 있는 d1과 d2인 경우에 각 선거구를 나눈 뒤 합을 계산한다.

여기서 어려웠던 것은 d1과 d2의 범위 설정이다. d1과 d2의 길이를 잘 설정해야 범위 안에 들어오는 경계선을 만들 수 있는데 생각보다 쉽지 않았다.

기본적으로 d1의 길이에 따라 왼쪽 대각선의 길이가 정해지고 d2의 길이에 따라 오른쪽 대각선의 길이가 정해진다.

첫 번째 예시에서 d1이 3이고 d2가 2인 경우를 생각해보자. 5개의 선거구로 나눌 수 있을까? 나눌 수 있다.

하지만 d1이 4가 되는 순간 d2의 길이와 관계없이 범위를 벗어나게 된다. 그렇다면 첫 번째 예시에서 d1의 최대 길이는 3일 것이다. 여기서 또 3은 어떻게 구할 수 있을까?

본인은 인덱스를 0부터 시작했기 때문에 첫 번째 예시에서 기준점은 (1,3)이다. 기준점을 보면 알 수 있듯이 d1은 현재 기준점의 y좌표만큼 갈 수 있다. 현재 기준점의 y좌표 길이보다 커지면 범위를 벗어나는 것이다.

그렇다면 d2는 어떨까?

이번에는 두 번째 예시를 보자. d2가 2인 경우가 최대 길이라는 것을 쉽게 알 수 있다. d2가 3이 되는 순간 범위를 벗어나기 때문이다. 그러면 d2의 최대 길이가 2인 것은 어떻게 구할 수 있을까?

현재 n은 7이고 기준점은 (1,4)이다. 여기서 n-y를 하게 되면 3이 된다. 이 뜻은 n-y 전까지가 d2의 최대 길이 범위가 된다는 것이다.

d1과 d2의 최대 길이 범위를 구했기 때문에 남은 것은 선거구를 나눠주는 방법과 각 선거구의 인구를 합하는 방법이다.

선거구를 나눠주기 전에 할 일은 경계선이 범위 안에 들어오는지 체크하는 것이다.

본인은 꼭짓점 4개를 기준으로 선거구를 나눌 것이기 때문에 이 꼭짓점들 중에 하나라도 범위를 벗어나면 경계선을 만들지 못한다고 판단하였다.

첫 번째 예시를 다시 봤을 때 꼭짓점을 사진과 같이 번호를 붙였다.(인덱스는 0부터 시작하므로 0,1,2,3)

각 꼭짓점의 좌표는 문제에 제시되어있다.

1번은 (x, y) , 2번은 (x + d1, y - d1), 3번은 (x + d2, y + d2), 4번은 (x + d2 + d1, y + d2 - d1)이다.

1번은 기준점이기 때문에 항상 범위 안에 있으므로 범위를 체크할 필요가 없다.

그러므로 2번, 3번, 4번 꼭짓점이 범위 안에 들어오면 벡터에 각 꼭짓점 좌표를 넣어준다.

위 그림 같은 경우 v[0] = {1,3} , v[1] = {3,1} , v[2] = {3,5}, v[3] = {5,3} 이런 식으로 저장한다.

이제 선거구를 나눠줘야 한다.

우선 TEMP 배열을 모두 5로 초기화를 시켜준다. 그 이후에 4번 반복문을 돌면서 1, 2, 3, 4번 선거구를 정할 것이다.

1번 선거구의 경우에 행은 2번 꼭짓점 전까지, 열은 1번 꼭짓점까지 포함한다.

2번 선거구의 경우에 행은 2번 꼭짓점까지, 열은 n-1까지 포함한다.

3, 4번 선거구는 아래서부터 탐색한다.

3번 선거구의 경우에 행은 2번 꼭짓점까지, 열은 4번 꼭짓점 전까지 포함한다.

4번 선거구의 경우에 행은 3번 꼭짓점 전까지, 열은 n-1까지 포함한다.

각 선거구마다 규칙이 다르기 때문에 범위를 잘 설정해가며 선거구를 나눠야 한다.

1번 선거구는 1번 선거구의 행이 1번 꼭짓점의 행과 같거나 커지면 열의 범위를 하나씩 줄이면 된다.

2번 선거구는 2번 선거구의 행이 1번 꼭짓점의 행보다 커지면 열의 시작 지점을 한 칸씩 앞으로 간다.

3번 선거구는 3번 선거구의 행이 4번 꼭짓점의 행보다 작을 때 열의 범위를 하나씩 줄이면 된다.

4번 선거구는 4번 선거구의 행이 4번 꼭짓점의 행과 같거나 작을 때 열의 시작 지점을 한 칸씩 앞으로 간다.

이 부분은 코드를 보면 이해가 쉬울 것이다.

이런 식으로 선거구를 다 나눴으면 각 선거구의 인구의 합을 구해야 한다.

선거구의 인구의 최댓값에서 최솟값을 빼야 하기 때문에 본인은 각 선거구의 합을 배열에 저장한 뒤 정렬을 통해 그 차이를 구하였다. 이 차이가 한 번 기준점을 정하고 경계선을 정상적으로 그렸을 때 각 선거구 인구의 최댓값과 최솟값의 차이기 때문에 min 함수를 이용하여 그 차이를 업데이트해준다. 그리고 마지막에 최솟값을 출력해주면 된다.

[소스 코드]

#include <bits/stdc++.h>
using namespace std;
int n;
int MAP[21][21];
int TEMP[21][21];
vector<pair<int, int>> v;
int SUM[6];
int MIN = 2147483647;
bool check(int x, int y, int d1, int d2) {
	if (x + d1 >= n || y - d1 < 0) return false;
	else if (x + d2 >= n || y + d2 >= n) return false;
	else if (x + d2 + d1 >= n || y + d2 - d1 < 0) return false;
	return true;
}
void calculate() {
	int sum = 0;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			SUM[TEMP[i][j]] += MAP[i][j];
		}
	}
	sort(SUM, SUM + 6);
	sum = SUM[5] - SUM[1];
	MIN = min(MIN, sum);
	fill(SUM, SUM + 6, 0);
}
void setting() {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			TEMP[i][j] = 5;
		}
	}
}
void go() {
	setting();
	int cnt = 0;
	for (int i = 0; i < v[1].first; i++) {
		if (i >= v[0].first) cnt++;
		for (int j = 0; j <= v[0].second - cnt; j++) {
			TEMP[i][j] = 1;
		}
	}
	cnt = 0;
	for (int i = 0; i <= v[2].first; i++) {
		if (i > v[0].first) cnt++;
		for (int j = v[0].second + 1 + cnt; j < n; j++) {
			TEMP[i][j] = 2;
		}
	}
	cnt = 0;
	for (int i = n - 1; i >= v[1].first; i--) {
		if (i < v[3].first) cnt++;
		for (int j = 0; j < v[3].second - cnt; j++) {
			TEMP[i][j] = 3;
		}
	}
	cnt = 0;
	for (int i = n - 1; i > v[2].first; i--) {
		if (i <= v[3].first) cnt++;
		for (int j = v[3].second + cnt; j < n; j++) {
			TEMP[i][j] = 4;
		}
	}
	v.clear();
}
void INPUT() {
	cin >> n;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> MAP[i][j];
		}
	}
}
void solve() {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			for (int d1 = 1; d1 <= j; d1++) {
				for (int d2 = 1; d2 < n - j; d2++) {
					if (check(i, j, d1, d2)) {
						v.push_back({ i,j });
						v.push_back({ i + d1, j - d1 });
						v.push_back({ i + d2, j + d2 });
						v.push_back({ i + d1 + d2, j - d1 + d2 });
						go();
						calculate();
					}
				}
			}
		}
	}
	cout << MIN;
}
int main() {
	cin.tie(0);
	ios::sync_with_stdio(0);
	INPUT();
	solve();
	return 0;
}

 

[문제 링크]

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

 

14891번: 톱니바퀴

총 8개의 톱니를 가지고 있는 톱니바퀴 4개가 아래 그림과 같이 일렬로 놓여져 있다. 또, 톱니는 N극 또는 S극 중 하나를 나타내고 있다. 톱니바퀴에는 번호가 매겨져 있는데, 가장 왼쪽 톱니바퀴가 1번, 그 오른쪽은 2번, 그 오른쪽은 3번, 가장 오른쪽 톱니바퀴는 4번이다. 이때, 톱니바퀴를 총 K번 회전시키려고 한다. 톱니바퀴의 회전은 한 칸을 기준으로 한다. 회전은 시계 방향과 반시계 방향이 있고, 아래 그림과 같이 회전한다. 톱니바퀴를 회전시키려

www.acmicpc.net

 

[문제 풀이]

삼성 기출 시뮬레이션 문제이다.

다른 삼성 기출문제들보다는 난이도가 쉬운 편이라고 생각한다.

톱니바퀴가 4개있고 각 톱니바퀴는 8개의 톱니로 이루어져 있다. 그리고 톱니는 N극(0) 또는 S극(1)으로 이루어져 있다.

각 톱니바퀴는 맞닿은 극이 서로 다르면 회전을 한다. 즉, n번째 톱니바퀴의 2번째 톱니와 n+1번째 톱니바퀴의 6번째 톱니의 극이 서로 다르면 회전을 한다. (톱니는 0부터 시작한다고 가정)

톱니바퀴를 K번 회전시킨 후 조건에 따라 점수를 더한다.

회전 후 각 톱니바퀴의 12시 방향이 S극이면 점수를 더하는데 1번 톱니바퀴는 1점, 2번 톱니바퀴는 2점, 3번 톱니바퀴는 4점, 4번 톱니바퀴는 8점이다. 합산한 점수를 출력하면 된다.

왼쪽 톱니바퀴부터 1번, 2번, 3번, 4번 톱니바퀴이다.

그림과 같은 톱니바퀴가 주어진다면 아래와 같은 숫자로 나타낼 수 있다.

10101111
01111101
11001110
00000010

그 다음엔 K가 주어지고 회전시킬 톱니바퀴 번호와 방향을 입력받는다.

예를 들어 2 1이면 2번 톱니바퀴를 시계방향으로 돌리는 것이다. 1 -1이면 1번 톱니바퀴를 반시계 방향으로 돌리는 것이다.

본인은 문제를 쉽게 풀기 위해 톱니바퀴를 덱으로 선언하여 풀었다.

문제에는 톱니바퀴가 1번부터 시작했지만 본인은 0번부터 시작하였다. 그래서 처음 톱니바퀴 번호를 입력받을 때 -1을 해준다.

회전할 톱니바퀴를 찾기 위해 벡터와 큐를 사용하였다. 또한, 톱니바퀴의 방문 체크를 해주었다.

여기서 중요한 것은 K번 입력받는 톱니바퀴들은 그 방향으로 무조건 회전을 한다. 인접한 톱니바퀴와 극이 같다고 해서 회전을 안 하는 것이 아니다.

그리고 이 문제는 회전하기 전의 상태에서 맞닿은 극이 서로 다르면 서로 다른 방향으로 회전한다. 즉, 입력받은 톱니바퀴를 해당 방향으로 회전시킨 뒤 인접한 톱니바퀴와 맞닿은 극이 서로 다른지를 보는 것이 아니다. 본인은 이 부분에서 문제를 잘못 이해하여 계속 틀렸다.

다시 문제로 돌아와서 처음 입력받는 톱니바퀴 번호와 방향을 큐와 벡터에 넣어준다. 큐는 회전할 톱니바퀴를 찾기 위해서 사용하고, 벡터는 톱니바퀴 정보를 저장하고 실제로 회전시키기 위해 사용한다.

큐가 빌 때까지 반복하면서 회전할 큐를 찾는다. 여기서 톱니바퀴가 0번째가 아닌 경우, 3번째가 아닌 경우로 나뉜다.

0번째가 아닌 경우는 현재 톱니바퀴와 그 왼쪽 톱니바퀴의 맞닿은 극이 서로 다른지, 방문한 톱니바퀴인지 체크한다.

3번째가 아닌 경우는 현재 톱니바퀴와 그 오른쪽 톱니바퀴의 맞닿은 극이 서로 다른지, 방문한 톱니바퀴인지 체크한다.

방문하지 않았고, 맞닿은 극이 서로 다르면 큐와 벡터에 톱니바퀴 번호와 반대 방향(방향 * -1)을 넣어준 뒤 해당 톱니바퀴를 방문 체크해준다.

큐를 통해 회전할 톱니바퀴를 다 찾았으면 반복문을 빠져나오고 벡터에 저장된 회전할 톱니바퀴들을 실제로 회전시킨다.

톱니바퀴는 덱으로 선언했기 때문에 숫자를 앞뒤로 넣고 빼는 것이 쉽다.

시계 방향인 경우 뒤에 숫자를 저장한 뒤 삭제하고 저장한 숫자를 앞에 넣어주면 된다.

반시계 방향인 경우 앞에 숫자를 저장한 뒤 삭제하고 저장한 숫자를 뒤에 넣어주면 된다.

이 작업을 K번 반복한 뒤 마지막에 점수를 더해주면 된다.

K번 반복할 때 방문 체크 배열을 초기화해주고 벡터를 초기화해줘야 한다.

[소스 코드]

#include <bits/stdc++.h>
using namespace std;
int k; //회전 횟수
bool check[4];
deque<int> dq[4]; // 톱니바퀴 상태 저장 및 회전하기 위한 덱

queue<pair<int, int>> q; // 회전할 톱니바퀴를 저장하기 위한 큐
vector<pair<int, int>> v; // 회전할 톱니바퀴들을 실제로 방향에 따라 회전시키기 위한 벡터
void rotate() {
	for (int i = 0; i < v.size(); i++) { // 톱니바퀴 회전 
		int cur_num = v[i].first;
		int cur_d = v[i].second;
		if (cur_d == 1) { // 시계 방향이면
			int tmp = dq[cur_num].back(); // 현재 톱니바퀴의 마지막 숫자를 tmp에 저장
			dq[cur_num].pop_back(); // 현재 톱니바퀴의 마지막 숫자 삭제
			dq[cur_num].push_front(tmp); // 현재 톱니바퀴의 앞에다가 tmp 삽입
		}
		else { // 시계 반대방향이면
			int tmp = dq[cur_num].front(); // 현재 톱니바퀴의 첫번째 숫자를 tmp에 저장
			dq[cur_num].pop_front(); // 현재 톱니바퀴의 첫번째 숫자 삭제
			dq[cur_num].push_back(tmp); // 현재 톱니바퀴의 뒤에다가 tmp 삽입
		}
	}
}
void check_rotate() {
	cin >> k;
	for (int i = 0; i < k; i++) {
		memset(check, false, sizeof(check));
		v.clear();
		int num, d;
		cin >> num >> d;

		num -= 1; // 인덱스를 0부터 시작하기 위해 입력되는 num값에서 -1
		v.push_back({ num,d }); // 처음 입력받는 톱니바퀴와 방향을 푸쉬
		q.push({ num, d }); // 처음 입력받는 톱니바퀴와 방향을 푸쉬
		check[num] = true; // 해당 톱니바퀴 방문체크
		while (!q.empty()) {
			int cur_num = q.front().first; // 현재 톱니바퀴 숫자
			int cur_d = q.front().second; // 회전할 방향
			q.pop();

			if (cur_num != 0) { // 톱니바퀴가 첫번째가 아니라면
				if (dq[cur_num - 1][2] != dq[cur_num][6] && !check[cur_num - 1]) {
					// 왼쪽 톱니바퀴와 극이 다르고 왼쪽 톱니바퀴를 방문하지 않았다면
					q.push({ cur_num - 1, cur_d * -1 }); // 왼쪽 톱니바퀴 번호와 반대 방향을 큐에 푸쉬
					v.push_back({ cur_num - 1, cur_d * -1 }); // 왼쪽 톱니바퀴 번호와 반대 방향을 벡터에 푸쉬
					check[cur_num - 1] = true; // 왼쪽 톱니바퀴 방문 체크
				}
			}
			if (cur_num != 3) { // 톱니바퀴가 마지막이 아니라면
				if (dq[cur_num][2] != dq[cur_num + 1][6] && !check[cur_num + 1]) {
					// 오른쪽 톱니바퀴와 극이 다르고 오른쪽 톱니바퀴를 방문하지 않았다면
					q.push({ cur_num + 1, cur_d * -1 }); // 오른쪽 톱니바퀴 번호와 반대 방향을 큐에 푸쉬
					v.push_back({ cur_num + 1, cur_d * -1 }); // 오른쪽 톱니바퀴 번호와 반대 방향을 벡터에 푸쉬
					check[cur_num + 1] = true; // 오른쪽 톱니바퀴 방문 체크
				}
			}
		}

		rotate();
	}
}

void count() {
	int cnt = 0;
	if (dq[0][0] == 1) cnt += 1;
	if (dq[1][0] == 1) cnt += 2;
	if (dq[2][0] == 1) cnt += 4;
	if (dq[3][0] == 1) cnt += 8;
	cout << cnt;
}
void INPUT() {
	for (int i = 0; i < 4; i++) {
		string str;
		cin >> str;
		for (int j = 0; j < 8; j++) {
			dq[i].push_back(str[j] - '0'); // 톱니바퀴 처음 상태 저장
		}
	}
}
void solve() {
	INPUT();
	check_rotate();
	count();
}
int main() {
	cin.tie(0); ios::sync_with_stdio(0);
	solve();
	return 0;
}

 

[문제 링크]

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

 

17822번: 원판 돌리기

반지름이 1, 2, ..., N인 원판이 크기가 작아지는 순으로 바닥에 놓여있고, 원판의 중심은 모두 같다. 원판의 반지름이 i이면, 그 원판을 i번째 원판이라고 한다. 각각의 원판에는 M개의 정수가 적혀있고, i번째 원판에 적힌 j번째 수의 위치는 (i, j)로 표현한다. 수의 위치는 다음을 만족한다. (i, 1)은 (i, 2), (i, M)과 인접하다. (i, M)은 (i, M-1), (i, 1)과 인접하다. (i, j)는 (i, j-1), (i, j

www.acmicpc.net

 


 

[문제 풀이]

 

전형적인 삼성의 시뮬레이션 문제이다.

 

처음에 원판에 있는 수들의 인접하는 조건이 나와있다.

 

[문제 조건1]

 

  • (i, 1)은 (i, 2), (i, M)과 인접하다.
  • (i, M)은 (i, M-1), (i, 1)과 인접하다.
  • (i, j)는 (i, j-1), (i, j+1)과 인접하다. (2 ≤ j ≤ M-1)
  • (1, j)는 (2, j)와 인접하다.
  • (N, j)는 (N-1, j)와 인접하다.
  • (i, j)는 (i-1, j), (i+1, j)와 인접하다. (2 ≤ i ≤ N-1)
 
복잡하게 설명되어 있는 것 같지만 그림으로 보면 이해가 쉽다.
 
문제 예시처럼 N=3, M=4인 경우를 살펴보겠다.
 
 

다음과 같은 원판이 있을 때 반지름이 1인 원판은 [1, 1, 2, 3]이다.

 

반지름이 1인 원판에서 첫 번째 1의 인접한 수는 같은 원판의 양 옆 숫자 1,3과 두 번째 원판의 같은 자리에 있는 5이다.

 

반지름이 2인 원판에서 5의 인접한 수는 같은 원판의 양 옆 숫자 2, 2와 첫 번째 원판과 세 번째 원판의 같은 자리에 있는 1, 3이다.

 

반지름이 3인 원판에서 1의 인접한 수는 같은 원판의 양 옆 숫자 3, 3과 두 번째 원판의 같은 자리에 있는 2이다.

 

이런 식으로 인접한 수의 조건을 만족하게 된다.

 

우리는 이 원판을 돌려야 한다. 원판은 시계방향으로 돌릴 수 있고, 반시계 방향으로 돌릴 수 있다.

 

첫 번째 원판을 시계방향으로 1번 회전시킨다면 [1, 1, 2 ,3] -> [3, 1, 1, 2]가 될 것이다. 여기서 시계방향으로 한 번 더 회전시킨다면 [3, 1, 1, 2] -> [2, 3, 1, 1]이 될 것이다.

 

두 번째 원판을 반시계 방향으로 1번 회전시킨다면 [5, 2, 4, 2] -> [2, 4, 2, 5]가 될 것이다. 

 

[문제 조건2]

 

원판을 아래와 같은 방법으로 총 T번 회전시키려고 한다. 원판의 회전 방법은 미리 정해져 있고, i번째 회전할 때 사용하는 변수는 xi, di, ki이다.

  1. 번호가 xi의 배수인 원판을 di방향으로 ki칸 회전시킨다. di가 0인 경우는 시계 방향, 1인 경우는 반시계 방향이다.
  2. 원판에 수가 남아 있으면, 인접하면서 수가 같은 것을 모두 찾는다.
    1. 그러한 수가 있는 경우에는 원판에서 인접하면서 같은 수를 모두 지운다.
    2. 없는 경우에는 원판에 적힌 수의 평균을 구하고, 평균보다 큰 수에서 1을 빼고, 작은 수에는 1을 더한다.

원판을 T번 회전시킨 후 원판에 적힌 수의 합을 구해보자.

 

문제 조건이 복잡해 보이면서도 간단해 보인다. 1번부터 차근차근 살펴보자.

 

1. 번호가 xi의 배수인 원판을 di방향으로 ki칸 회전시킨다. di가 0인 경우는 시계 방향, 1인 경우는 반시계 방향이다.

 

소스 코드의 move함수에 해당하는 내용이다.

 

처음에 Xi의 배수라는 조건을 봤을 때 "Xi가 1이면 전부 다 돌리는 건가?"라는 생각을 하고 문제 조건 1을 봤는데 Xi는 2이상이다. 이런 시뮬레이션 문제에서는 조건 하나하나를 자세히 봐야 한다고 생각한다. (그래야 삽질을 덜 하고 빠르게 풀 수 있다고 생각)

 

원판의 회전 방법은 미리 정해져 있기 때문에 T번을 반복하면서 Xi, Di, Ki 변수를 받아준다. 나는 x, d, K 변수를 사용했다.

 

예를 들어 2 0 1이라는 수를 입력했으면 2의 배수인 원판을 시계 방향으로 1번 회전시키라는 뜻이다.

 

x의 배수인 원판을 돌리라고 했으므로 원판의 번호가 x로 나누어 떨어질 때 돌리면 된다.

 

시계 방향일 때와 반시계 방향일 때로 나누어서 원판을 돌려준다.

 

시계 방향일 때는 원판의 m번째 숫자를 tmp에 저장한다. (i번째 원판이라면 MAP[i][m], m은 원판의 마지막 인덱스)

 

자신의 인덱스의 왼쪽에 있는 숫자를 현재 인덱스에 저장한다. 이것을 m번째 인덱스부터 2번째 인덱스까지 진행한다.

 

마지막으로 tmp에 저장했던 m번째 숫자를 1번째 인덱스에 저장하면 시계방향으로 1칸 회전한 효과를 얻을 수 있다.

 

반시계 방향일 때는 반대로 진행한다. 원판의 1번째 숫자를 tmp에 저장한다. (i번째 원판이라면 MAP[i][1], 인덱스는 1부터 시작)

 

자신의 인덱스의 오른쪽에 있는 숫자를 현재 인덱스에 저장한다. 이것을 1번째 인덱스부터 m-1 인덱스까지 진행한다.

 

마지막으로 tmp에 저장했던 1번째 숫자를 m번째 인덱스에 저장하면 반시계 방향으로 1칸 회전한 효과를 얻을 수 있다.

 

원판에 수가 남아 있으면, 인접하면서 수가 같은 것을 모두 찾는다.

  1. 그러한 수가 있는 경우에는 원판에서 인접하면서 같은 수를 모두 지운다.
  2. 없는 경우에는 원판에 적힌 수의 평균을 구하고, 평균보다 큰 수에서 1을 빼고, 작은 수에는 1을 더한다.

 

소스 코드의 remove 함수에 해당하는 내용이다.

 

인은 이 구현을 위해 TEMP라는 2차원 배열을 사용했다. 처음에 TEMP배열을 사용하지 않아서 5번 예제의 답을 구할 수 없었다. TEMP배열을 사용한 이유는 뒤에서 설명하겠다.

 

또한, flag 변수를 통해 2번을 진행할 것인지 아닌지에 대해 정하였다. 모든 원판을 탐색하면서 1번의 경우가 한 번이라도 나온다면 2번을 진행하지 않고 한 번도 나오지 않았다면 2번을 진행하였다.

 

문제 조건 1을 보면 첫 번째 원판에서는 각 숫자의 인접한 수가 3개이다. 마지막 원판도 각 숫자의 인접한 수가 3개이다. 그 외에는 4개의 수가 인접한다. 이를 통해 조건문을 원판이 첫 번째일 때, 마지막일 때, 그 외일 때로 나누었다.

 

여기서 중요한 것은 1번 인덱스와 m번 인덱스가 인접한다는 것이다. 1번 인덱스와 m번 인덱스가 인접한다는 것을 어떻게 구현해야 할지 생각해봐야 한다.

 

본인은 2개의 idx 변수를 만들어서 현재 인덱스에서 -1 한 것과 +1 한 것을 저장하였다. 

 

예를 들어 현재 인덱스가 2이면 idx1과 idx2에는 각각 1, 3을 저장한다. 

 

만약 idx1이 0이 된다면 현재 인덱스가 1이라는 뜻이므로 idx1을 m으로 바꿔준다. 

 

만약 idx2가 m+1이 된다면 현재 인덱스가 m이라는 뜻이므로 idx2를 1로 바꿔준다. 

 

그리고 MAP의 현재 인덱스와 인접한 인덱스의 숫자가 같다면 해당 인덱스들의 숫자를 TEMP배열에서 0으로 바꿔준다. 

여기서 숫자 0은 문제에서 x를 표현한다.

 

비교는 MAP에서 하고 실제로 바꿔주는 건 TEMP에서 하는 이유가 뭘까?

 

5번 예제를 생각해보자.

 

4 6 3
1 2 3 4 5 6
2 3 4 5 6 7
3 4 5 6 7 8
4 5 6 7 8 9
2 1 4
3 0 1
2 1 2

 

여기서 첫 번째 회전을 보면 2의 배수인 원판을 반시계 방향으로 4칸 회전을 시킨다.

 

회전을 시키고 난 뒤 원판의 상태이다.

 

1 2 3 4 5 6

6 7 2 3 4 5

3 4 5 6 7 8

8 9 4 5 6 7

 

첫 번째 회전에서는 각각의 원판에서 같은 숫자의 인접한 수가 없기 때문에 평균을 구하고 평균보다 큰 수는 -1, 작은 수는 +1을 한다. 총합은 120이고 숫자가 24개이기 때문에 평균은 5가 된다. 5보다 큰 수는 +1, 작은 수는 -1을 하였다.

 

여기서 본인이 실수한 부분을 설명하자면 문제에서 평균보다 큰 수는 +1을 하라고 했고, 작은 수는 -1을 하라고 했다.

 

근데 예제를 보니 평균보다 큰 수는 -1을 했는데 같은 숫자도 +1을 했길래 당연히 같거나 작은 수를 +1을 해줬다.

 

하지만 문제를 대충 보면 안 되는 이유가 여기에 있다.

 

 

22/6은 3.6666667이다. 즉, 3.67보다 작은 수는 +1, 큰 수는 -1을 하라는 의미이다. 본인은 문제를 대충 읽고 22/6을 정수로 변환하여 계산을 해서 계속 틀렸다. 이런 실수는 주의해야 할 것 같다. (소스 코드의 solve() 함수)

 

다시 문제로 돌아와서 계산을 한 뒤 원판의 상태이다.

2 3 4 5 5 5

5 6 3 4 5 5

4 5 5 5 6 7

7 8 5 5 5 6

본인은 처음에 MAP 하나를 사용하여 현재 보고 있는 숫자에서 인접한 수가 같으면 0으로 바꿔주었다.

 

하지만 그럴 경우 제대로 결과가 나오지 않는다.

 

현재 이 원판의 상태에서 두 번째 회전(3의 배수인 원판을 시계방향으로 1칸 회전)을 정상적으로 진행하면 이런 결과가 나온다.

 

2 3 4 5 5 5             2 3 4 0 0 0

5 6 3 4 5 5             0 6 3 4 0 0

7 4 5 5 5 6             0 4 0 0 0 0

7 8 5 5 5 6             0 8 0 0 0 0

 

왼쪽은 회전을 한 상태이고, 오른쪽은 인접한 숫자가 같을 때 0으로 표시한 상태이다.

 

TEMP 배열을 이용하면 이렇게 정상적인 결과를 얻을 수 있지만 MAP 하나로 remove 함수를 사용하면 다른 결과를 얻게 된다.

 

2 3 4 5 5 5           2 3 4 0 0 0

5 6 3 4 5 5           5 6 3 4 0 0

7 4 5 5 5 6           0 4 0 0 0 0

7 8 5 5 5 6           0 8 0 0 5 0

 

이렇게 다른 결과를 얻을 수 있기 때문에 TEMP 배열을 하나 더 선언하여 MAP에서 인접한 수가 같다면 TEMP 배열에서 0으로 바꿔주었다. 그러면 정상적인 결과를 얻을 수 있다.

 

또한, remove 함수를 진행할 때 TEMP배열이 0이라면 건너뛰었다. 그 이유는 건너뛰지 않아도 값에는 변화가 없지만 x와 x(숫자로는 0과 0)가 인접한 것은 문제 조건과 맞지 않기 때문이다.

 

이런 방식으로 T번 회전을 모두 마친 뒤에 남아 있는 원판의 수를 모두 더하면 된다.

 

[소스 코드]

#include <bits/stdc++.h>
using namespace std;
int n, m, t, x, d, K;
int MAP[51][51];
int TEMP[51][51];
int ans;
void setting() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			TEMP[i][j] = MAP[i][j];
		}
	}
}

void setting1() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			MAP[i][j] = TEMP[i][j];
		}
	}
}

void move(int x, int d, int K) {
	cin >> x >> d >> K;

	for (int i = 1; i <= n; i++) {
		if (i % x == 0) {
			if (d == 0) {
				for (int j = 0; j < K; j++) {
					int tmp = MAP[i][m];
					for (int k = m; k >= 2; k--) {
						MAP[i][k] = MAP[i][k - 1];
					}
					MAP[i][1] = tmp;
				}
			}
			else if (d == 1) {
				for (int j = 0; j < K; j++) {
					int tmp = MAP[i][1];
					for (int k = 1; k <= m - 1; k++) {
						MAP[i][k] = MAP[i][k + 1];
					}
					MAP[i][m] = tmp;
				}
			}
		}
	}
}

bool remove() {
	bool flag = false;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (TEMP[i][j] == 0) continue;
			if (i == 1) {
				int idx1 = j - 1;
				int idx2 = j + 1;
				if (idx1 == 0) idx1 = m;
				if (idx2 == m + 1) idx2 = 1;
				if (MAP[i][j] == MAP[i][idx1]) {
					TEMP[i][j] = 0;
					TEMP[i][idx1] = 0;
					flag = true;
				}
				else if (MAP[i][j] == MAP[i][idx2]) {
					TEMP[i][j] = 0;
					TEMP[i][idx2] = 0;
					flag = true;
				}
				else if (MAP[i][j] == MAP[i + 1][j]) {
					TEMP[i][j] = 0;
					TEMP[i + 1][j] = 0;
					flag = true;
				}
			}
			else if (i == n) {
				int idx1 = j - 1;
				int idx2 = j + 1;
				if (idx1 == 0) idx1 = m;
				if (idx2 == m + 1) idx2 = 1;
				if (MAP[i][j] == MAP[i][idx1]) {
					TEMP[i][j] = 0;
					TEMP[i][idx1] = 0;
					flag = true;
				}
				else if (MAP[i][j] == MAP[i][idx2]) {
					TEMP[i][j] = 0;
					TEMP[i][idx2] = 0;
					flag = true;
				}
				else if (MAP[i][j] == MAP[i - 1][j]) {
					TEMP[i][j] = 0;
					TEMP[i - 1][j] = 0;
					flag = true;
				}
			}
			else {
				int idx1 = j - 1;
				int idx2 = j + 1;
				if (idx1 == 0) idx1 = m;
				if (idx2 == m + 1) idx2 = 1;
				if (MAP[i][j] == MAP[i][idx1]) {
					TEMP[i][j] = 0;
					TEMP[i][idx1] = 0;
					flag = true;
				}
				else if (MAP[i][j] == MAP[i][idx2]) {
					TEMP[i][j] = 0;
					TEMP[i][idx2] = 0;
					flag = true;
				}
				else if (MAP[i][j] == MAP[i + 1][j]) {
					TEMP[i][j] = 0;
					TEMP[i + 1][j] = 0;
					flag = true;
				}
				else if (MAP[i][j] == MAP[i - 1][j]) {
					TEMP[i][j] = 0;
					TEMP[i - 1][j] = 0;
					flag = true;
				}
			}
		}
	}

	if (flag) return true;
	else return false;
}

void solve() {
	int cnt = 0;
	int sum = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (TEMP[i][j] != 0) {
				sum += TEMP[i][j];
				cnt++;
			}
		}
	}
	double avg = (double)sum / cnt;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (TEMP[i][j] > avg) {
				TEMP[i][j]--;
			}
			else if (TEMP[i][j] < avg && TEMP[i][j] > 0) {
				TEMP[i][j]++;
			}
		}
	}

}

void answer_count() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (MAP[i][j] != 0) {
				ans += MAP[i][j];
			}
		}
	}
	cout << ans;
}
void INPUT() {
	cin >> n >> m >> t;

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> MAP[i][j];
		}
	}
}
int main() {
	cin.tie(0);
	ios::sync_with_stdio(0);
	INPUT();
	for (int i = 0; i < t; i++) {
		move(x, d, K);
		setting();
		if (!remove()) solve();
		setting1();
	}
	answer_count();
	
	return 0;
}

 

 

 

+ Recent posts