Problem Solving/Baekjoon Online Judge

[백준] 16234 인구이동

jujube bat 2020. 3. 26. 16:39

[문제이해]

1. NxN 크기의 땅이 있다. 각각의 땅에는 나라가 하나씩 존재한다.

2. r행 c열에 있는 나라는 map[r][c] 명이 살고 있다.

3. 모든 나라는 1x1 크기이고 모든 국경선은 정사각형 형태이다.

4. 인구이동이 시작된다. 인구 이동은 아래와 같은 방법으로 진행된다. 더 이상 인구 이동이 없을때까지 지속된다.

  • 국경선을 공유하는 두 나라의 인구차이가 L명 이상, R명 이하라면, 두 나라가 공유하는 국경선을 하루동안 연다.
  • 위의 조건에 의해 열어야하는 국경선이 모두 열렸다면, 인구 이동을 시작한다.
  • 국경선이 열려있어 인접한 칸만을 이용해 이동할 수 있으면, 그 나라를 하루동안은 연합 이라고 한다.
  • 연합을 이루고 있는 각 칸의 인구수는 (연합의 인구수)/(연합을 이루고 있는 칸의 개수)가 된다. (소수점은 버린다.)
  • 연합을 해체하고, 모든 국경선을 닫는다.

5. N,L,R이 주어졌을 때 인구 이동이 몇 번 발생하는지 구하는 프로그램을 작성해야한다.

6. 입력은 아래와 같다.

  • 첫째 줄에 N, L, R이 주어진다. (1 ≤ N ≤ 50, 1 ≤ L ≤ R ≤ 100)
  • 둘째 줄부터 N개의 줄에 각 나라의 인구수가 주어진다. r행 c열에 주어지는 정수는 A[r][c]의 값이다. (0 ≤ A[r][c] ≤ 100)
  • 인구 이동이 발생하는 횟수가 2,000번 보다 작거나 같은 입력만 주어진다.

 

[설계]

1. DFS 또는 BFS를 통해 연합을 구한다. 연합은 BFS 방문 배열에 숫자로 기록하여 구분한다.

2. 연합을 구할때마다 그 연합을 이루고 있는 각 칸의 인구수를 구해둔다.

3. 로직은 다음과 같이 진행된다. 국경선을 BFS로 연다. 1개라도 국경선이 열린다면, 인구이동을 진행한다. 

4. 국경선이 하나라도 열리지 않다면, 인구이동을 하지않고 종료한다.

5. 인구이동 횟수를 출력한다.

 

[소스]

 

//dfs로 그룹짓고.
//지은 그룹에 대해서 맵변경하고(++인구이동횟수)
//그룹 하나라도 지을 수 없으면 종료
#include<iostream>
#include<queue>
#include<string.h>
using namespace std;

typedef struct {
	int x, y;
}pos;

int N, L, R;
int map[50][50];
int res;
int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
int cnt=1;
int visited[50][50];
int arr[2501];

bool bfs(int sx, int sy, int cnt) { 
	queue<pos> q;
	bool flag=false;

	int numOfUnited = 1;
	int sumOfUnited = map[sx][sy];

	visited[sx][sy] = cnt;
	q.push({ sx, sy });

	while (!q.empty()) {
		int x = q.front().x, y = q.front().y;
		q.pop();

		for (int i = 0; i < 4; i++) {
			int nx = x + dx[i], ny = y + dy[i];
			if (nx >= N || nx < 0 || ny >= N || ny < 0) continue;
			if (visited[nx][ny]) continue;

			int diff = abs(map[x][y] - map[nx][ny]);
            
             //인접한 국가와 인구수 차이가 L보다 같거나 크고 R보다 작거나 같다면
             //국경선을 연다. 
			if (diff >= L && diff <= R) {
				flag = true;
				visited[nx][ny] = cnt;

				numOfUnited++;
				sumOfUnited += map[nx][ny];

				q.push({ nx,ny });
			}
		}
	}

	arr[cnt] = sumOfUnited / numOfUnited; //연합국의 인구수를 구해서 배열해 저장해둔다.

	if (!flag)
		visited[sx][sy] = 0;

	return flag;
}

bool openLoad() { //국경선을 여는 함수

	bool flag = false;
	memset(visited, 0, sizeof(visited));
	cnt = 1;

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			if (visited[i][j] == 0) {
				
				bool signal = bfs(i, j, cnt);

				if (!flag) {
					if (signal)
						flag = true;
				}

				cnt++;
			}
		}
	}

	return flag;
}

void moving() { //인구 이동을 하는 함수
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			if (visited[i][j]) {
				map[i][j] = arr[visited[i][j]];
			}
		}
	}
}

void solve() {
	while (1) {
		if (openLoad()) {
			moving();
			res++;
		}
		else { //더 이상 인구이동이 불가능하면(국경선이 열리지 않는다면) 결과를 출력하고 종료.
			cout << res;
			return;
		}
	}
}

int main() {
	cin >> N >> L >> R;

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			cin >> map[i][j];
		}
	}

	solve();

	return 0;
}