알고리즘/[ADV] 삼성 SW 역량 테스트 A형

[코드트리] 토끼와 경주 (삼성 SW 역량테스트 2023 상반기 오전 2번, B형)

피로물든딸기 2024. 7. 28. 23:18
반응형

삼성 A형 전체 링크

삼성 B형 전체 링크

 

2022 하반기 이후 문제 풀이 시간이 3시간  4시간으로 변경,

A형 1문제 + B형 문제 1문제가 출제됩니다.

 

참고

- B형 필수 : 우선순위 큐 Priority Queue

- BOJ 10825 : 국영수

 

https://www.codetree.ai/training-field/frequent-problems/problems/rabit-and-race

이후 설명 및 입/출력은 링크 참고

 

2차원 좌표 N x M100,000 * 100,000이다. → 2차원 배열 선언 시, 메모리 초과

토끼가 움직이는 거리 d <= 1,000,000,000d 만큼 움직이면 시간 초과

 

따라서, 토끼 구조체에서 좌표를 관리하고, 효율적으로 움직여야 한다.

 

좌표를 관리할 구조체토끼를 관리할 구조체를 선언한다.

typedef long long ll;

typedef struct st1
{
	int r;
	int c;
}RC;

typedef struct st2
{
	int r;
	int c;
	int id;
	int d;
	int check;
	int jumpCount;
	ll score;
}RABBIT;

 

main은 다음과 같다.

typedef long long ll;

#define START (100)
#define RACE (200)
#define CHANGE (300)
#define SELECT (400)

...

int main(void)
{
	scanf("%d", &Q);

	for (int q = 0; q < Q; q++)
	{
		scanf("%d", &COMMAND);

		if (COMMAND == START) input();
		else if (COMMAND == RACE)
		{
			int k, s;
			scanf("%d %d", &k, &s);
			race(k, s);
		}
		else if (COMMAND == CHANGE)
		{
			int pid_t, l;
			scanf("%d %d", &pid_t, &l);
			change(pid_t, l);
		}
		else if (COMMAND == SELECT)
		{
			select();
		}
	}

	return 0;
}

우선순위 큐

 

경주는 최대 2,000번, 한 경주 당 최대 100번 반복하고 2,000마리의 토끼 중 우선순위가 높은 토끼를 구해야 한다.

완전 탐색으로 토끼를 선택할 경우, 2,000 * 100 * 2,000 = 400,000,000 = 4억이므로 역시 시간 초과다.

 

따라서 우선순위 큐를 구현해야 한다.

C++priority_queue 대신 배열로 직접 구현하면 토끼의 점수를 계산하기 편하고, 디버깅도 쉽다.

우선순위 큐의 직접 구현은  B형 필수 : 우선순위 큐 Priority Queue BOJ 10825 : 국영수를 참고하자.

 

토끼에 대한 우선순위 큐 push, pop은 다음과 같다.

RABBIT heap[MAX];
int hn;

RABBIT pop()
{
	RABBIT ret, tmp;

	ret = heap[1];
	heap[1] = heap[hn];
	heap[hn].jumpCount = 0x7fff0000;
	heap[hn].r = 100001; // r + c = overflow 방지를 위해 0x7fff0000을 사용하지 않음.
	heap[hn].c = 100001;
	heap[hn--].id = 0x7fff0000;

	for (register int i = 1; i * 2 <= hn;)
	{
		if (isMinRabbit(heap[i], heap[i * 2]) && isMinRabbit(heap[i], heap[i * 2 + 1])) break;
		else if (isMinRabbit(heap[i * 2], heap[i * 2 + 1]))
		{
			tmp = heap[i * 2];
			heap[i * 2] = heap[i];
			heap[i] = tmp;

			i = i * 2;
		}
		else
		{
			tmp = heap[i * 2 + 1];
			heap[i * 2 + 1] = heap[i];
			heap[i] = tmp;

			i = i * 2 + 1;
		}
	}

	return ret;
}

void push(RABBIT x)
{
	RABBIT tmp;

	heap[++hn] = x;

	for (int i = hn; i > 1; i /= 2)
	{
		if (isMinRabbit(heap[i / 2], heap[i])) return;

		tmp = heap[i / 2];
		heap[i / 2] = heap[i];
		heap[i] = tmp;
	}
}

 

토끼를 선택하기 위한 우선순위는 isMinRabbit에서 결정된다.

문제에서 주어진 우선순위 조건을 순서대로 구현하면 된다.

int isMinRabbit(RABBIT a, RABBIT b)
{
	// 1. 점프 횟수가 적은 토끼
	if (a.jumpCount != b.jumpCount) return a.jumpCount < b.jumpCount;

	// 2. 현재 서 있는 행 번호 + 열 번호가 작은 토끼
	int aRC = a.r + a.c;
	int bRC = b.r + b.c;
	if (aRC != bRC) return aRC < bRC;

	// 3. 행 번호가 작은 토끼
	if (a.r != b.r) return a.r < b.r;

	// 4. 열 번호가 작은 토끼
	if (a.c != b.c) return a.c < b.c;

	// 5. ID가 작은 토끼
	return a.id < b.id;
}

경주 시작 준비

 

문제에서 주어진 대로 입력을 받는다.

토끼의 경우는 고유번호(id)이동해야 하는 거리(d)를 입력받고 우선순위 큐에 push한다.

void input()
{
	scanf("%d %d %d", &N, &M, &P);

	hn = 0; // 우선순위 큐 초기화

	for (int p = 0; p < P; p++)
	{
		int id, d;
		scanf("%d %d", &id, &d);

		RABBIT rabbit = { 0 };
		rabbit.id = id;
		rabbit.d = d;
		rabbit.r = 1; // (1, 1)에서 시작
		rabbit.c = 1;

		push(rabbit);
	}
}

경주 진행

 

경주 진행은 다음과 같이 실행된다.

 

0) 현재 라운드에 해당하는 토끼의 check를 모두 0으로 초기화한다. (check = 해당 race에서 선택된 토끼)

ㄴ 우선순위 큐는 완전 이진트리므로, index(= 1 ~ hn)를 이용해 배열에 직접 접근할 수 있다.

 

1) 우선 순위가 높은 토끼를 우선순위 큐에서 pop한다.

2) 해당 토끼가 갈 수 있는 4개의 위치를 구해본다. (rc[0 ~ 3])

3) 4개의 위치 중 우선순위가 높은 칸을 선택한다.

4) 남은 토끼의 점수를 증가한다. 

ㄴ 현재 우선순위 큐에는 선택한 토끼를 제외한 모든 토끼가 있다. 

ㄴ 마찬가지로 index(= 1 ~ hn)로 접근할 수 있다. (pop을 했기 때문에 0번의 hn과 값이 다르다.)

 

5) 선택한 토끼를 움직이고, 점프 횟수와 check 표시를 하고 다시 우선순위 큐에 넣는다.

6) 뽑힌 토끼 중 가장 우선순위가 높은 (isMaxRabbit) 토끼에게 추가 점수를 준다.

void race(int k, int s)
{
	// 0. 선택된 토끼 표시 초기화
	for (int i = 1; i <= hn; i++) heap[i].check = 0;

	for (int count = 0; count < k; count++)
	{
		// 1. 우선 순위가 높은 토끼 선택
		RABBIT rabbit = pop();

		// 2. 4방향 좌표 확인		
		RC rc[4] = { 0 }; // up, down, left, right

		rc[0] = moveUp(rabbit);
		rc[1] = moveDown(rabbit);
		rc[2] = moveLeft(rabbit);
		rc[3] = moveRight(rabbit);

		// 3. 우선순위가 높은 칸 선택
		RC moveRC = { 0 };
		for (int d = 0; d < 4; d++)
			if (isMaxRC(rc[d], moveRC))
				moveRC = rc[d];

		// 4. 나머지 토끼 점수 추가
		for (int i = 1; i <= hn; i++)
			heap[i].score += (moveRC.r + moveRC.c);

		// 5. 선택된 토끼 위치 변경 및 정보 갱신
		rabbit.r = moveRC.r;
		rabbit.c = moveRC.c;
		rabbit.jumpCount++;
		rabbit.check = 1;

		push(rabbit); // 다시 우선순위 큐에 추가
	}

	// 6. 한 번이라도 뽑힌 (check = 1) 토끼 중, 가장 우선순위(isMaxRabbit)가 높은 토끼 선택
	int index = -1;
	RABBIT selectRabbit = { 0 };
	for (int i = 1; i <= hn; i++)
	{
		if (heap[i].check == 0) continue;

		if (isMaxRabbit(heap[i], selectRabbit))
		{
			index = i;
			selectRabbit = heap[i];
		}
	}

	heap[index].score += s;
}

 

여기서 moveUp을 구현해 보자.

 

먼저 규칙을 찾는다.

만약 토끼가 N = 6이고 row = 2에서 moveUp을 해보자.

move = 1 → row = 1

move = 2 → row = 0 (좌표 범위 밖) → row = 2, 방향 변경 ↓

move = 3 → row = 3

...

move = 7 → row = 7 (좌표 범위 밖) → row = 5, 방향 변경 ↑

move = 8 → row = 4

...

move = 10 → row = 2 (원래 위치)

 

그림으로 그려보면 다음과 같다.

 

만약 row = 3인 경우라면 아래의 그림과 같다.

 

그림을 보고 규칙을 찾아보면 다음과 같다.

 

1) 이동할 위치 2 * ( N - 1 ) 주기로 반복된다. 따라서 move = d % (2 * ( N - 1) ) 칸만 움직이면 된다.

2) rabbit.r - move1보다 크다땅을 넘지 않는 칸이 된다. 

3) move가 rabbit.r + N - 2 이하인 경우는 내려가는 칸이다.

ㄴ 이 경우 실제 좌표는 move - (rabbit.r - 1) + 1이 된다.

 

4) 나머지의 경우N에서 (움직이는 횟수 - (땅을 넘지 않는 검은색 칸) - (내려가는 빨간 칸 N - 1개)) 만큼 올라간다.

실제 좌표는 N - (move - (rabbit.r - 1) - (N - 1))가 된다.

 

코드로 구현하면 다음과 같다.

RC moveUp(RABBIT rabbit)
{
	RC ret = { 0 };

	int r = rabbit.r;
	int d = rabbit.d;

	int move = d % (2 * (N - 1)); // 움직여야 할 칸 수

	if ((rabbit.r - move) >= 1) ret.r = rabbit.r - move;
	else
	{
		if (move <= (rabbit.r + N - 2))
			ret.r = move - (rabbit.r - 1) + 1;
		else
			ret.r = N - (move - (rabbit.r - 1) - (N - 1));
	}

	ret.c = rabbit.c;

	return ret;
}

 

moveDown은 복잡하게 다시 규칙을 찾을 필요 없이 대칭을 이용하면 된다.

(전체 좌표 + 1) 에서 현재 토끼의 좌표를 빼고 올라간 후,

그 결과를 (전체 좌표 + 1)에서 해당 좌표를 빼면 내려가는 것과 같은 결과가 된다.

RC moveDown(RABBIT rabbit)
{
	// 대칭을 이용
	RABBIT mirror = rabbit;

	mirror.r = (N + 1) - rabbit.r;

	RC ret = moveUp(mirror);

	ret.r = (N + 1) - ret.r;

	return ret;
}

 

moveLeftNM으로, rc로 변경하면 되고, moveRight도 마찬가지로 대칭을 이용하면 된다.

RC moveLeft(RABBIT rabbit)
{
	RC ret = { 0 };

	int c = rabbit.c;
	int d = rabbit.d;

	int move = d % (2 * (M - 1)); // 움직여야 할 칸 수

	if ((rabbit.c - move) >= 1) ret.c = rabbit.c - move;
	else
	{
		if (move <= (rabbit.c + M - 2))
			ret.c = move - (rabbit.c - 1) + 1;
		else
			ret.c = M - (move - (rabbit.c - 1) - (M - 1));
	}

	ret.r = rabbit.r;

	return ret;
}

RC moveRight(RABBIT rabbit)
{
	// 대칭을 이용
	RABBIT mirror = rabbit;

	mirror.c = (M + 1) - rabbit.c;

	RC ret = moveLeft(mirror);

	ret.c = (M + 1) - ret.c;

	return ret;
}

 

그리고 위에서 구한 4개의 좌표배열에 저장한다.

	// 2. 4방향 좌표 확인		
	RC rc[4] = { 0 }; // up, down, left, right

	rc[0] = moveUp(rabbit);
	rc[1] = moveDown(rabbit);
	rc[2] = moveLeft(rabbit);
	rc[3] = moveRight(rabbit);

 

4개 좌표우선순위가 높은 칸을 선택한다.

	// 3. 우선순위가 높은 칸 선택
	RC moveRC = { 0 };
	for (int d = 0; d < 4; d++)
		if (isMaxRC(rc[d], moveRC))
			moveRC = rc[d];

 

문제에서 좌표에 대해 요구하는 우선순위는 다음과 같다.

int isMaxRC(RC a, RC b)
{
	// 1. 행 번호 + 열 번호가 큰 칸
	int aRC = a.r + a.c;
	int bRC = b.r + b.c;
	if (aRC != bRC) return aRC > bRC;

	// 2. 행 번호가 큰 칸
	if (a.r != b.r) return a.r > b.r;

	// 3. 열 번호가 큰 칸
	return a.c > b.c;
}

 

우선순위가 높은 좌표를 이용해 나머지 토끼에 모두 점수를 추가한다.

점수는 int 범위를 넘어갈 수 있어서 구조체에 long long으로 선언해두었다.

우선순위 큐는 완전 이진트리이기 때문에 1부터 heap index = hn까지 배열로 접근할 수 있다.

또한, 현재 우선순위 큐에는 race 초기에 선택한 토끼가 없다. (해당 토끼는 점수를 추가하지 않음)

	// 4. 나머지 토끼 점수 추가
	for (int i = 1; i <= hn; i++)
		heap[i].score += (moveRC.r + moveRC.c);

 

이제 선택한 토끼를 실제로 이동하고, 점프 횟수를 증가하고, 선택 표시를 한다.

그리고 다시 우선순위 큐에 push한다.

	// 5. 선택된 토끼 위치 변경 및 정보 갱신
	rabbit.r = moveRC.r;
	rabbit.c = moveRC.c;
	rabbit.jumpCount++;
	rabbit.check = 1;

	push(rabbit); // 다시 우선순위 큐에 추가

 

check 표시된 토끼 중에 가장 우선순위가 높은 토끼를 선택해 점수에 S를 추가한다.

여기서 우선순위는 우선순위 큐의 우선순위와 다른 조건이다.

	// 6. 한 번이라도 뽑힌 (check = 1) 토끼 중, 가장 우선순위(isMaxRabbit)가 높은 토끼 선택
	int index = -1;
	RABBIT selectRabbit = { 0 };
	for (int i = 1; i <= hn; i++)
	{
		if (heap[i].check == 0) continue;

		if (isMaxRabbit(heap[i], selectRabbit))
		{
			index = i;
			selectRabbit = heap[i];
		}
	}

	heap[index].score += S;

 

문제에서 요구한 조건대로 isMaxRabbit을 구현하였다.

int isMaxRabbit(RABBIT a, RABBIT b)
{
	// 1. 현재 서 있는 행 번호 + 열 번호가 큰 토끼
	int aRC = a.r + a.c;
	int bRC = b.r + b.c;
	if (aRC != bRC) return aRC > bRC;

	// 2. 행 번호가 큰 토끼
	if (a.r != b.r) return a.r > b.r;

	// 3. 열 번호가 큰 토끼
	if (a.c != b.c) return a.c > b.c;

	// 4. ID가 큰 토끼
	return a.id > b.id;
}

이동거리 변경

 

전체 heap에서 해당되는 ID이동거리를 L배하면 된다.

void change(int pid_t, int l)
{
	for (int i = 1; i <= hn; i++)
		if (heap[i].id == pid_t) heap[i].d *= l;
}

최고의 토끼 선정

 

전체 heap에서 가장 점수가 높은 토끼를 찾아서 출력한다.

void select()
{
	ll maxScore = 0;
	for (int i = 1; i <= hn; i++)
		if (maxScore < heap[i].score) maxScore = heap[i].score;

	printf("%lld\n", maxScore);
}

전체 코드는 다음과 같다.

#include <stdio.h>

typedef long long ll;

#define MAX (2000 + 100)

#define START (100)
#define RACE (200)
#define CHANGE (300)
#define SELECT (400)

int Q, COMMAND, N, M, P;

typedef struct st1
{
	int r;
	int c;
}RC;

typedef struct st2
{
	int r;
	int c;
	int id;
	int d;
	int check;
	int jumpCount;
	ll score;
}RABBIT;

RABBIT heap[MAX];
int hn;

int isMinRabbit(RABBIT a, RABBIT b)
{
	// 1. 점프 횟수가 적은 토끼
	if (a.jumpCount != b.jumpCount) return a.jumpCount < b.jumpCount;

	// 2. 현재 서 있는 행 번호 + 열 번호가 작은 토끼
	int aRC = a.r + a.c;
	int bRC = b.r + b.c;
	if (aRC != bRC) return aRC < bRC;

	// 3. 행 번호가 작은 토끼
	if (a.r != b.r) return a.r < b.r;

	// 4. 열 번호가 작은 토끼
	if (a.c != b.c) return a.c < b.c;

	// 5. ID가 작은 토끼
	return a.id < b.id;
}

int isMaxRC(RC a, RC b)
{
	// 1. 행 번호 + 열 번호가 큰 칸
	int aRC = a.r + a.c;
	int bRC = b.r + b.c;
	if (aRC != bRC) return aRC > bRC;

	// 2. 행 번호가 큰 칸
	if (a.r != b.r) return a.r > b.r;

	// 3. 열 번호가 큰 칸
	return a.c > b.c;
}

int isMaxRabbit(RABBIT a, RABBIT b)
{
	// 1. 현재 서 있는 행 번호 + 열 번호가 큰 토끼
	int aRC = a.r + a.c;
	int bRC = b.r + b.c;
	if (aRC != bRC) return aRC > bRC;

	// 2. 행 번호가 큰 토끼
	if (a.r != b.r) return a.r > b.r;

	// 3. 열 번호가 큰 토끼
	if (a.c != b.c) return a.c > b.c;

	// 4. ID가 큰 토끼
	return a.id > b.id;
}

RABBIT pop()
{
	RABBIT ret, tmp;

	ret = heap[1];
	heap[1] = heap[hn];
	heap[hn].jumpCount = 0x7fff0000;
	heap[hn].r = 100001; // r + c = overflow 방지를 위해 0x7fff0000을 사용하지 않음.
	heap[hn].c = 100001;
	heap[hn--].id = 0x7fff0000;

	for (register int i = 1; i * 2 <= hn;)
	{
		if (isMinRabbit(heap[i], heap[i * 2]) && isMinRabbit(heap[i], heap[i * 2 + 1])) break;
		else if (isMinRabbit(heap[i * 2], heap[i * 2 + 1]))
		{
			tmp = heap[i * 2];
			heap[i * 2] = heap[i];
			heap[i] = tmp;

			i = i * 2;
		}
		else
		{
			tmp = heap[i * 2 + 1];
			heap[i * 2 + 1] = heap[i];
			heap[i] = tmp;

			i = i * 2 + 1;
		}
	}

	return ret;
}

void push(RABBIT x)
{
	RABBIT tmp;

	heap[++hn] = x;

	for (int i = hn; i > 1; i /= 2)
	{
		if (isMinRabbit(heap[i / 2], heap[i])) return;

		tmp = heap[i / 2];
		heap[i / 2] = heap[i];
		heap[i] = tmp;
	}
}

void printRabbit(RABBIT r)
{
	printf("rabbit id %d, d %d,  (%d, %d), jump : %d, score %d\n", r.id, r.d, r.r, r.c, r.jumpCount, r.score);
}

void printAll()
{
	for (int i = 1; i <= hn; i++)
		printRabbit(heap[i]);
	printf("======================================\n");
}

void input()
{
	scanf("%d %d %d", &N, &M, &P);

	hn = 0; // 우선순위 큐 초기화

	for (int p = 0; p < P; p++)
	{
		int id, d;
		scanf("%d %d", &id, &d);

		RABBIT rabbit = { 0 };
		rabbit.id = id;
		rabbit.d = d;
		rabbit.r = 1; // (1, 1)에서 시작
		rabbit.c = 1;

		push(rabbit);
	}
}

RC moveUp(RABBIT rabbit)
{
	RC ret = { 0 };

	int r = rabbit.r;
	int d = rabbit.d;

	int move = d % (2 * (N - 1)); // 움직여야 할 칸 수

	if ((rabbit.r - move) >= 1) ret.r = rabbit.r - move;
	else
	{
		if (move <= (rabbit.r + N - 2))
			ret.r = move - (rabbit.r - 1) + 1;
		else
			ret.r = N - (move - (rabbit.r - 1) - (N - 1));
	}

	ret.c = rabbit.c;

	return ret;
}

RC moveDown(RABBIT rabbit)
{
	// 대칭을 이용
	RABBIT mirror = rabbit;

	mirror.r = (N + 1) - rabbit.r;

	RC ret = moveUp(mirror);

	ret.r = (N + 1) - ret.r;

	return ret;
}

RC moveLeft(RABBIT rabbit)
{
	RC ret = { 0 };

	int c = rabbit.c;
	int d = rabbit.d;

	int move = d % (2 * (M - 1)); // 움직여야 할 칸 수

	if ((rabbit.c - move) >= 1) ret.c = rabbit.c - move;
	else
	{
		if (move <= (rabbit.c + M - 2))
			ret.c = move - (rabbit.c - 1) + 1;
		else
			ret.c = M - (move - (rabbit.c - 1) - (M - 1));
	}

	ret.r = rabbit.r;

	return ret;
}

RC moveRight(RABBIT rabbit)
{
	// 대칭을 이용
	RABBIT mirror = rabbit;

	mirror.c = (M + 1) - rabbit.c;

	RC ret = moveLeft(mirror);

	ret.c = (M + 1) - ret.c;

	return ret;
}

void race(int k, int s)
{
	// 0. 선택된 토끼 표시 초기화
	for (int i = 1; i <= hn; i++) heap[i].check = 0;

	for (int count = 0; count < k; count++)
	{
		// 1. 우선 순위가 높은 토끼 선택
		RABBIT rabbit = pop();

		// 2. 4방향 좌표 확인		
		RC rc[4] = { 0 }; // up, down, left, right

		rc[0] = moveUp(rabbit);
		rc[1] = moveDown(rabbit);
		rc[2] = moveLeft(rabbit);
		rc[3] = moveRight(rabbit);

		// 3. 우선순위가 높은 칸 선택
		RC moveRC = { 0 };
		for (int d = 0; d < 4; d++)
			if (isMaxRC(rc[d], moveRC))
				moveRC = rc[d];

		// 4. 나머지 토끼 점수 추가
		for (int i = 1; i <= hn; i++)
			heap[i].score += (moveRC.r + moveRC.c);

		// 5. 선택된 토끼 위치 변경 및 정보 갱신
		rabbit.r = moveRC.r;
		rabbit.c = moveRC.c;
		rabbit.jumpCount++;
		rabbit.check = 1;

		push(rabbit); // 다시 우선순위 큐에 추가
	}

	// 6. 한 번이라도 뽑힌 (check = 1) 토끼 중, 가장 우선순위(isMaxRabbit)가 높은 토끼 선택
	int index = -1;
	RABBIT selectRabbit = { 0 };
	for (int i = 1; i <= hn; i++)
	{
		if (heap[i].check == 0) continue;

		if (isMaxRabbit(heap[i], selectRabbit))
		{
			index = i;
			selectRabbit = heap[i];
		}
	}

	heap[index].score += s;
}

void change(int pid_t, int l)
{
	for (int i = 1; i <= hn; i++)
		if (heap[i].id == pid_t) heap[i].d *= l;
}

void select()
{
	ll maxScore = 0;
	for (int i = 1; i <= hn; i++)
		if (maxScore < heap[i].score) maxScore = heap[i].score;

	printf("%lld\n", maxScore);
}

int main(void)
{
	scanf("%d", &Q);

	for (int q = 0; q < Q; q++)
	{
		scanf("%d", &COMMAND);

		if (COMMAND == START) input();
		else if (COMMAND == RACE)
		{
			int k, s;
			scanf("%d %d", &k, &s);
			race(k, s);
		}
		else if (COMMAND == CHANGE)
		{
			int pid_t, l;
			scanf("%d %d", &pid_t, &l);
			change(pid_t, l);
		}
		else if (COMMAND == SELECT)
		{
			select();
		}
	}

	return 0;
}
반응형