ML/AI/SW Developer

BAEK21610 마법사 상어와 비바라기(C++, python)

1. 문제 설명

  • 문제 링크
  • N×N인 격자
  • 절차
    1. 모든 구름이 di 방향으로 si칸 이동한다.
    2. 각 구름에서 비가 내려 구름이 있는 칸의 바구니에 저장된 물의 양이 1 증가한다.
    3. 구름이 모두 사라진다.
    4. 2에서 물이 증가한 칸 (r, c)에 물복사버그 마법을 시전한다. 물복사버그 마법을 사용하면, 대각선 방향으로 거리가 1인 칸에 물이 있는 바구니의 수만큼 (r, c)에 있는 바구니의 물이 양이 증가한다.
      • 이때는 이동과 다르게 경계를 넘어가는 칸은 대각선 방향으로 거리가 1인 칸이 아니다.
      • 예를 들어, (N, 2)에서 인접한 대각선 칸은 (N-1, 1), (N-1, 3)이고, (N, N)에서 인접한 대각선 칸은 (N-1, N-1)뿐이다.
    5. 바구니에 저장된 물의 양이 2 이상인 모든 칸에 구름이 생기고, 물의 양이 2 줄어든다. 이때 구름이 생기는 칸은 3에서 구름이 사라진 칸이 아니어야 한다.
첫째 줄에 N, M이 주어진다.

둘째 줄부터 N개의 줄에는 N개의 정수가 주어진다. r번째 행의 c번째 정수는 A[r][c]를 의미한다.

다음 M개의 줄에는 이동의 정보 di, si가 순서대로 한 줄에 하나씩 주어진다.

2. 풀이

  • 시물레이션 문제
    1. 구름 이동
    • 정해진 방향으로 정해진 칸수 만큼 이동
    • 쓸때 없는 반복을 줄이기 위해 나머지 연산 활용
      • commands[idx].second % N;
        1. 구름 있는 칸 물량 증가
    • 사라질 구름 표시
      1. 물복사 마법 시전
    • 경계를 넘어가지 않고, 대각선 방향으로 거리가 1인 칸만 증가
      1. 구름 없애기
      2. 구름 생성
    • 구름이 없어진 칸 제외 (2에서 표시한 배열 사용)
    • 물양이 2 이상인 곳에 구름 생성
      1. 1-5 반복
    • 명령 종료 후 물의양 다 더하기

3. 코드

  • c++
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

// 맵크기
int N;
// 움직임 횟수
int M;

// 8방위 - 구름이동
int dx[9] = { 0, 0, -1, -1, -1, 0, 1, 1, 1 };
int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };

// 대각선 4방위 - 물의 양 증가(주변 바구니 탐색)
int dx_diagonal[4] = { -1,1,-1,1 };
int dy_diagonal[4] = { -1,-1,1,1 };

// 물의 양이 증가한 바구니 리스트
vector<pair<int, int>> increased;

// 구름이 사라진 위치 표시
bool disappear[51][51];

// 각 바구니의 물의 양 정보
int map[51][51];

// 이동 명령
vector<pair<int, int>> commands;

// 구름 이동
vector<pair<int, int>> Move(vector<pair<int, int>>& clouds, int idx) {
	int d = commands[idx].first;
	int mv = commands[idx].second % N;
	vector<pair<int, int>> new_clouds;
	bool temp[51][51];
	memset(temp, false, sizeof(temp));

	for (int i = 0; i < clouds.size(); i++) {
		// 구름 별 이 동후 좌표 설정
		clouds[i].first = clouds[i].first + (dx[d] * mv);
		if (clouds[i].first > N) clouds[i].first = clouds[i].first - N;
		else if (clouds[i].first < 1) clouds[i].first = clouds[i].first + N;

		clouds[i].second = clouds[i].second + (dy[d] * mv);
		if (clouds[i].second > N) clouds[i].second = clouds[i].second - N;
		else if (clouds[i].second < 1) clouds[i].second = clouds[i].second + N;
		
		// 겹친 구름 하나로 만들기
		if (!temp[clouds[i].first][clouds[i].second]) {
			temp[clouds[i].first][clouds[i].second] = true;
			new_clouds.push_back(make_pair(clouds[i].first, clouds[i].second));
		}
	}
	// 이동된 구름들 좌표 반환
	return new_clouds;
}

// 물복사 버그
void Bug(vector<pair<int, int>>& clouds) {
	for (int i = 0; i < clouds.size(); i++) {
		int x = clouds[i].first;
		int y = clouds[i].second;
		int iswater = 0;
		for (int j = 0; j < 4; j++) {
			int nx = x + dx_diagonal[j];
			int ny = y + dy_diagonal[j];
			if (nx < 1 || nx > N || ny < 1 || ny > N) continue;
			if (map[nx][ny] > 0) iswater++;
		}
		map[x][y] += iswater;
	}
}

void Print() {
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			cout << map[i][j] << "\t";
		}
		cout << endl;
	}
	cout << endl;
}

int main() {
	// 맵크기 및 이동 횟수 설정
	cin >> N >> M;

	// 기본 물의 양 정보 입력
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			cin >> map[i][j];
		}
	}

	// 이동 명령 입력
	for (int i = 0; i < M; i++) {
		int d, s;
		cin >> d >> s;
		commands.push_back(make_pair(d, s));
	}

	vector<pair<int, int>> clouds;
	// 초기 구름
	clouds.push_back(make_pair(N, 1));
	clouds.push_back(make_pair(N, 2));
	clouds.push_back(make_pair(N-1, 1));
	clouds.push_back(make_pair(N-1, 2));
	int idx = 0;
	while (idx < M) {
		// 1. 이동
		clouds = Move(clouds, idx);
		
		//cout << idx+1 << endl;
		//cout << "구름 이동 후 " << clouds .size() << endl;
		//Print();
		
		memset(disappear, false, sizeof(disappear));
		// 2. 구름 있는 칸 물량 증가
		for (int i = 0; i < clouds.size(); i++) {
			map[clouds[i].first][clouds[i].second]++;
			disappear[clouds[i].first][clouds[i].second] = true;
		}

		//cout << "물량 증가후" << endl;
		//Print();

		// 3. 구름 사라짐 ( 나중에 처리)
		// 4. 2에서 물이 증가한 칸에 물복사 버그
		Bug(clouds);

		//cout << "물 복사 버그 사용 후" << endl;
		//Print();

		// 5. 바구니 물의 양이 2이상인 곳에서 모두 그름 생성, 물의 양 2 감소
		clouds.clear();
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= N; j++) {
				// 물의 양이 2인 곳에, 구름이 사라졌던 칸이 아니면
				if (map[i][j] >= 2 && !disappear[i][j]) {
					// 구름생성
					clouds.push_back(make_pair(i, j));
					// 물양 감소
					map[i][j] -= 2;
				}
			}
		}

		//cout << "구름 생성 후" << endl;
		//Print();

		// 다음순서로 진행
		idx++;
	}

	// 6. 끝난 후 물의 양 계산
	int answer = 0;
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			answer += map[i][j];
		}
	}

	cout << answer << endl;
	return 0;
}
  • python
    • 코드 중복을 줄이기 위해서 check_boundary 함수 추가
    • 알고리즘은 C++과 동일
# 구름 이동시켜 새로운 구름 리스트 반환
    # 격자정보, 방향, 이동거리
def move_cloud(grid, clouds, dir, distance):
    global N, dx, dy

    def check_boundary(value):
        # 오른쪽, 아래쪽으로 벗어나면 - N
        if value >= N: return value - N
        # 왼쪽이나, 위쪽으로 벗어나면 + N
        elif value < 0: return value + N
        # 격자 안이면 그냥 반환
        return value

    # 실제 이동거리(반복 제외)
    actual_distance = distance % N
    # 이동 후 구름 리스트
    new_clouds = []
    # 중복방지용 visited 배열
    temp = [[False for _ in range(N)] for __ in range(N)]

    for idx in range(len(clouds)):
        # x 좌표 계산
        clouds[idx][0] += (dx[dir] * actual_distance)
        clouds[idx][0] = check_boundary(clouds[idx][0])
        # y 좌표 계산
        clouds[idx][1] += (dy[dir] * actual_distance)
        clouds[idx][1] = check_boundary(clouds[idx][1])

        # 같은 좌표로 이동하는 구름 중복 방지
            # 방문한적 있는 좌표는 True 표시. 즉, 한번만 append
        if not temp[clouds[idx][0]][clouds[idx][1]]:
            temp[clouds[idx][0]][clouds[idx][1]] = True
            new_clouds.append([clouds[idx][0], clouds[idx][1]])
    return new_clouds

# 물복사 버그
def Bug(clouds, grid):
    global N, dx_diagonal, dy_diagonal

    # 구름이 있던곳 = 물의 양이 증가 한곳
    for x, y in clouds:
        # 증가할 물의양
        iswater = 0
        # 대각선 방향 검사
        for dir in range(4):
            nx = x + dx_diagonal[dir]
            ny = y + dy_diagonal[dir]
            if 0 <= nx < N and 0 <= ny < N: 
                # 물이 있는 바구니 수만큼 물의 양 증가
                if grid[nx][ny] > 0:
                    # 물이 있으면 증가
                    iswater += 1
        grid[x][y] += iswater

# main
# N, M 입력
N, M = map(int, input().split())

# 격자 정보 입력
grid = [list(map(int, input().split())) for _ in range(N)]

# commande 정보 입력
    # [방향, 이동 거리]
command = [list(map(int, input().split())) for _ in range(M)]

# command 방향
    # ←, ↖, ↑, ↗, →, ↘, ↓, ↙
dx = [ 0, 0, -1, -1, -1, 0, 1, 1, 1 ]
dy = [ 0, -1, -1, 0, 1, 1, 1, 0, -1 ]

# 물복사를 위한 대각선 방향
    # ↖, ↙, ↗, ↘
dx_diagonal = [ -1, 1, -1, 1 ]
dy_diagonal = [ -1, -1, 1, 1 ]

# 0. 초기 구름 좌표 0~N-1로 사용
clouds = [[N-1, 0], [N-1, 1], [N-2, 0],[N-2, 1]]

# M개의 command 실행
for di, si in command:
    # 1. 구름 이동
    clouds = move_cloud(grid, clouds, di, si)

    # 2. 구름이 있는 칸 물량 증가
        # 사라질 구름 표시
    disappear = [[False for _ in range(N)] for __ in range(N)]
    for x, y in clouds:
        # 구름이 있는 칸 물량 1 증가
        grid[x][y] += 1
        # 사라져야함으로 사라질 구름 표시
            # 물복사 버그를 진행해야 하기때문에 지금 바로 없애면 안됨
        disappear[x][y] = True

    # 3. 물복사 버그 시전
    Bug(clouds, grid)

    # 4. 이전 구름 정보 제거, 새로운 구름 생성
    clouds = []
    for x in range(N):
        for y in range(N):
            # 물양이 2이고, 구름이 사라진 곳이 아니면 구름 생성
            if grid[x][y] >= 2 and not disappear[x][y]:
                clouds.append([x, y])
                grid[x][y] -= 2
    
    # 다음 커맨드로 진행

# 5. 끝난 후 물의 양 계산
answer = 0
for x in range(N):
    for y in range(N):
        answer += grid[x][y]

print(answer)

4. 결과


C++로 예전에 풀엇던 알고리즘 문제를 python으로 복습해보았다. 실행시간은 느리지만 python이 간편하게 코드를 작성할 수 있도록 해주는 것 같다.