문제 링크


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

 

21775번: 가희와 자원 놀이

T턴에 걸쳐서, 각 턴에 수행된 연산 카드의 id를 한 줄에 하나씩 출력해 주세요.

www.acmicpc.net

 

접근 방법


일단 문제 풀이의 전체적인 프로세스는 아래와 같습니다.

 

 

 

 공용 공간의 카드 그리고 개인이 가지고 있는 카드는 HashMap을 사용하여 정보를 저장합니다.

 

또한 문제를 해결하기 위해 저는 2가지의 Class(구조체)를 정의하였습니다. 

1. Info(int id, String action, int num) -> 카드의 id, (next, acquire, release) 중 한 가지, 자원의 번호

2. UserPlace(HashMap myCards, boolean flag, Info info) -> 개인카드 덱, 수행할 acquire의 존재 유무, 존재한다면 수행해야 할 acquire의 정보

 

 이 두 가지의 자료구조와 위의 프로세스를 따라간다면 구현할 수 있는 문제였습니다.

자세한 내용은 아래 소스코드에 주석처리 하겠습니다.

 

소스 코드


import java.*;
import java.util.*;

public class boj21775 {
	
	static int[] turns;
	static ArrayList<Info> infos = new ArrayList<Info>();
	
	public static class Info {
		int id; String action; int num;
		
		public Info(int id, String action, int num) {
			this.id = id;
			this.action = action;
			this.num = num;
		}
	}
	
	public static class UserPlace {
		
		HashMap<Integer, Boolean> myCards;
		boolean flag;
		Info info;
		
		public UserPlace(HashMap myCards, boolean flag, Info info) {
			this.myCards = myCards;
			this.flag = flag;
			this.info = info;
		}
	}
	
	public static void solve(int n, int t) throws Exception {
		
		HashMap<Integer, Boolean> common = new HashMap<Integer, Boolean>(); //공용공간
		ArrayList<UserPlace> Users = new ArrayList<UserPlace>(); //각각의 유저들(idx로 참조)
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		StringBuilder sb = new StringBuilder();
		
		for(int i=0;i<n;i++) 
			Users.add(new UserPlace(new HashMap<Integer, Boolean>(), false, null));
		
		int pointer = 0; //더미 포인터
		
		for(int i=0;i<t;i++) {
			
            // 현재 유저
			UserPlace now = Users.get(turns[i]);
			
            //수행해야 할 acquire가 존재한다면
            if(now.flag) {
				boolean isContain = common.get(now.info.num);
				if(isContain) { //공용 공간에 acquire 자원이 있다면
					common.put(now.info.num, false); //공용공간의 자원 false
					now.myCards.put(now.info.num, true); //개인공간의 자원 true
					now.flag = false; //acquire 해제
				}
				sb.append(now.info.id + "\n");
			} //수행해야 할 acquire가 없다면
			else {
				
                //더미 포인터를 통해 수행해야할 연산을 가져옴
				Info info = infos.get(pointer);
				
				if(info.action.charAt(0) == 'n') //next인 경우
					sb.append(info.id + "\n");
				else if(info.action.charAt(0) == 'r') { //release인 경우
					now.myCards.put(info.num, false); //개인공간의 자원 false
					common.put(info.num, true); //공용공간의 자원 true
					sb.append(info.id + "\n");
				}
				else { //acquire인 경우
                	// 공용공간에 key가 없다는 것은 한 번도 해당 자원을 참조하지 않았다는 뜻
					if(!common.containsKey(info.num)) {
						common.put(info.num, false); //공용공간에 자원을 추가하며 사용불가 false
						now.myCards.put(info.num, true); //개인 공간의 자원 true
					}
					else { // 공용 공간에 자원이 참조된 적이 있을 때
						if(common.get(info.num)) { //공용공간에서 사용가능하다면
							common.put(info.num, false); //공용공간의 자원 false
							now.myCards.put(info.num, true); //개인 공간의 자원 true
							now.flag = false; //acquire flag 해제
						}
						else { //공용공간에서 사용불가능 하다면
							now.flag = true; // 수행해야 할 acquire true
							now.info = info; // 현재의 acquire 정보를 user에 update
						}
					}
					sb.append(info.id + "\n");
				}
                //더미 포인터 증가
				pointer++;
			}
		}
		
		bw.write(sb.toString());
		bw.close();
	}

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str = br.readLine();
		StringTokenizer st = new StringTokenizer(str," ");
		
		int n = Integer.parseInt(st.nextToken());
		int t = Integer.parseInt(st.nextToken());
		
		str = br.readLine();
		st = new StringTokenizer(str," ");
		turns = new int[t];
		for(int i=0;i<t;i++) {
			turns[i] = Integer.parseInt(st.nextToken()) - 1; //추 후 index로 참조하기위해 -1
		}
		
		for(int i=0;i<t;i++) {
			str = br.readLine();
			st = new StringTokenizer(str," ");
			int id = Integer.parseInt(st.nextToken());
			String action = st.nextToken();
			int num = 0; //0번 자원은 존재하지 않기 때문에 next인 경우 임시로 0번 자원 저장
			if(action.charAt(0) != 'n')
				num = Integer.parseInt(st.nextToken());
			infos.add(new Info(id, action, num)); 
		}
		br.close();
		
		solve(n, t);	
	}
}

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

[C++] 백준 20542 받아쓰기  (0) 2021.01.15
[C++] 백준 20543 폭탄 던지는 태영이  (0) 2021.01.15
[C++] 백준 19238 스타트 택시  (0) 2021.01.12
[C++] 백준 19237 어른 상어  (1) 2021.01.12
[C++] 백준 20544 공룡게임  (2) 2021.01.10

문제 링크


www.acmicpc.net/problem/20542

 

20542번: 받아쓰기

세계적인 기업 CTP(Chickens Threaten Programming)에 입사하기 위해서는 영어 받아쓰기 테스트를 통과해야 한다. 영어 받아쓰기는 채용 담당자가 불러주는 단어를 지원자가 받아쓰는 시험이다. CTP에서는

www.acmicpc.net

 

 

접근 방법


처음에 어떤 알고리즘을 적용해야 할지 몰라 문제를 못풀었습니다. 하지만 편집거리라는 알고리즘을 알고있다면 어렵지 않게 풀 수 있는 문제였습니다.

 편집거리 알고리즘은 LCS와 동작이 거의 비슷합니다. 하지만 차이점이 있다면 LCS는 일치하는 가장 긴 문자열을 찾아내기 위해 서로 일치하는 경우를 찾아 최댓값으로 갱신하였고, 편집거리는 가장 조금 수정하는 경우를 찾아내기 위해 서로 일치하지 않는 경우를 찾아 최소로 갱신한다는 차이가 있습니다.   

 

아래 블로그에 편집거리에 대한 자세한 설명이 있으니 참고 부탁드립니다.

hsp1116.tistory.com/41

 

편집 거리 알고리즘(Levenshtein Distance, Edit Distance)

편집 거리 알고리즘이란, 두 문자열의 유사도를 판단하는 알고리즘이다. 유사도를 판단하는 기준은, 어떠한 문자열을 삽입,삭제,변경을 몇 번이나 해서 바꿀수 있는지를 계산하여 그 최소값을

hsp1116.tistory.com

 

문제에서 유의해야 될 사항은 한 가지가 있습니다.

답안의 한 철자를 char g, 정답의 한 철자를 char a하고 한다면 

 g가 'i'일 때, a가 'i','j','l'이라면 정답처리를 하고,

 g가 'v'일 때, a가 'v','w'라면 정답처리를 한다는 것입니다.

 

즉, 편집거리 알고리즘을 진행하며 같은지 다른지에 대한 비교연산을 위와 같이 정의해서 풀어나가 문제를 해결할 수 있었습니다.

 

 

소스 코드


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

using namespace std;

int N, M;

string guess;
string ans;

bool isCorrect(char g, char a) {
	if (g == a)
		return true;
	else if (g == 'i' && (a == 'i' || a == 'j' || a == 'l'))
		return true;
	else if (g == 'v' && (a == 'v' || a == 'w'))
		return true;
	else
		return false;
}

int editDistance() {

	vector<vector<int>> dp(N + 1, vector<int>(M + 1, 0));

	for (int i = 1; i < N + 1; i++) //답안 
		dp[i][0] = i;
	for (int i = 1; i < M + 1; i++) //정답
		dp[0][i] = i;

	for (int i = 1; i < dp.size(); i++) { //답안
		for (int j = 1; j < dp[i].size(); j++) { //정답
			if (isCorrect(guess[i - 1], ans[j - 1])) //같다면 복제
				dp[i][j] = dp[i - 1][j - 1];
			else
				dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1; // 추가, 삭제, 수정 중 가장 적은 것
		}
	}
	return dp[N][M];
}

int main() {

	ios_base::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	cin >> N >> M;

	for (int i = 0; i < N; i++) {
		char spell; cin >> spell;
		guess += spell;
	}
	for (int i = 0; i < M; i++) {
		char spell; cin >> spell;
		ans += spell;
	}
	cout << editDistance() << "\n";
}

문제 링크


www.acmicpc.net/problem/20543

 

20543번: 폭탄 던지는 태영이

시험을 망친 태영이가 인하대학교에 폭탄을 던진다! 인하대학교는 N×N 크기의 정사각형 모양의 땅이다. 인하대학교의 모든 땅은 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)

www.acmicpc.net

 

접근 방법


 처음 이 문제를 접근하였을 때 시간초과를 해결하는 것이 어렵지 않았습니다. 하지만 풀이법을 떠올렸을 때 신선한 충격을 받게 되었습니다. 부분합을 적절히 활용하면 n^2의 복잡도로 해결할 수 있었습니다. 

 

만약 폭탄의 범위가 3인 경우 한 지역은 범위내에 있는 폭탄의 영향을 받습니다. 

 

x지점은 색이 칠해져 있는 부분들의 폭탄의 합과 같다는 것을 유추 할 수 있습니다.

그러면 한 지점의 폭탄의 수는 위의 정보를 활용하여 구할 수 있습니다.

 

문제에서 주어진 누적합의 배열을 sum[N], 각각 폭탄을 나타내는 배열을 bomb[N]이라 하였을 때

아래 그림의 정보를 활용하면 bomb[16]의 값은 다음과 같이 구할 수 있습니다.   

bomb[16] = 도형A(sum[11]) - 도형B(sum[7]) - 도형C(sum[10]) + 도형D(sum[6]) + bomb[4] + bomb[13] - bomb[1]

 

이 식을 문제에 맞게 일반화 시키면

(r = M/2일 때)

bomb[i][j] = sum[i-r][j-r] - sum[i-r-1][j-r] - sum[i-r][j-r-1] + sum[i-r-1][j-r-1] + bomb[i-M][j] + bomb[i][j-M] - bomb[i-M][j-M]

이와 같이 정의 할 수 있습니다.

 

소스 코드


#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

long long univ[2001][2001];
long long ans[2001][2001];
int N, M;

void printMap() {

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++)
			cout << ans[i][j] << " ";
		cout << "\n";
	}
}

void solve() {
	
	int r = M / 2;
	int start = r;
	int end = N - r;
	             
	for (int i = start; i < end; i++) {
		for (int j = start; j < end; j++) {
			ans[i][j] = univ[i - r][j - r];
			if (i - r - 1 >= 0) 
				ans[i][j] -= univ[i - r - 1][j - r];
			if (j - r - 1 >= 0) 
				ans[i][j] -= univ[i - r][j - r - 1];
			if (i - r - 1 >= 0 && j - r - 1 >= 0) 
				ans[i][j] += univ[i - r - 1][j - r - 1];
			if (i - M >= 0)
				ans[i][j] += ans[i - M][j];
			if (j - M >= 0)
				ans[i][j] += ans[i][j - M];
			if (i - M >= 0 && j - M >= 0)
				ans[i][j] -= ans[i - M][j - M];
		}
	}
}

int main() {

	ios_base::sync_with_stdio(0);
	cout.tie(0);
	cin.tie(0);

	cin >> N >> M;

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			long long num; cin >> num;
			univ[i][j] = -1 * num;
		}
	}

	solve();
	printMap();
}

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

[Java] 백준 21775 가희와 자원 놀이  (0) 2021.06.09
[C++] 백준 20542 받아쓰기  (0) 2021.01.15
[C++] 백준 19238 스타트 택시  (0) 2021.01.12
[C++] 백준 19237 어른 상어  (1) 2021.01.12
[C++] 백준 20544 공룡게임  (2) 2021.01.10

문제 링크


www.acmicpc.net/problem/19238

 

19238번: 스타트 택시

첫 줄에 N, M, 그리고 초기 연료의 양이 주어진다. (2 ≤ N ≤ 20, 1 ≤ M ≤ N2, 1 ≤ 초기 연료 ≤ 500,000) 연료는 무한히 많이 담을 수 있기 때문에, 초기 연료의 양을 넘어서 충전될 수도 있다. 다

www.acmicpc.net

 

접근 방법


문제를 해결하는 과정은 아래와 같습니다.

 

1. 현재 택시로 부터 가장 가까운 손님을 탐색하기 위해선 BFS를 사용해야 합니다. 이 때 거리가 같은 손님이 여러명 존재할 수 있기 때문에 가장 가까운 손님을 탐색완료하더라도 현재 레벨 까지는 계속해서 탐색을 해야합니다. 또한 탐색을 시작하기전 현재위치에 손님이 있는지도 먼저 검사를 해야합니다.

 

2. 손님의 위치가 탐색이 완료된다면 현재의 연료를 가지고 탐색한 위치로 이동 할 수 있는지 검사 후 이동을 합니다.

 

3. 1번 항목과 마찬가지로 BFS로 탐색을 하여 손님의 도착지 까지의 거리를 알아냅니다. 

 

4. 이후 현재의 연료를 가지고 이동 할 수 있는지 검사 후 이동을 시킵니다.  

 

5. 손님이 있던 좌표의 정보를 제거합니다.

 

6. 1번 항목부터 반복

 

자세한 사항은 소스코드에 주석을 달았습니다.

소스 코드


#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <string.h>
#define MAX 10000

using namespace std;

typedef struct coord {
	int x, y;
};

typedef struct tInfo {
	coord xy;
	int cost;
};

coord direction[4] = { {1,0}, {-1,0}, {0,1}, {0,-1} };
coord passengers[21][21];
int tMap[21][21];
int N, M;

bool inRange(int x, int y) {
	return (0 <= x && x < N && 0 <= y && y < N);
}

coord comp(coord one, coord other) {
	if (one.x < other.x)
		return one;
	else if (one.x == other.x) {
		if (one.y < other.y)
			return one;
		else
			return other;
	}
	else
		return other;
}

tInfo BFS(int x, int y, int state) { 
//state == 1 -> 택시가 손님에게
//state == 2 -> 손님을 태우고 목적지로

	queue <tInfo> q;
	vector<vector<bool>> visited(N, vector<bool>(N));
	coord ret = {MAX, MAX};
	int dist = 0;

	q.push({ x,y,0 });
	visited[x][y] = true;

	if (state == 1 && passengers[x][y].x != -1 && passengers[x][y].x != -1) //현재 위치에 손님이 있다면
		return { {x,y},0 };

	while (!q.empty()) {

		bool flag = false;
		int size = q.size();

		while (--size >= 0) { // 레벨 탐색

			int now_x = q.front().xy.x;
			int now_y = q.front().xy.y;
			int cost = q.front().cost;
			q.pop();

			for (int i = 0; i < 4; i++) {

				int next_x = now_x + direction[i].x;
				int next_y = now_y + direction[i].y;

				if (inRange(next_x, next_y)) {
					if (!visited[next_x][next_y] && tMap[next_x][next_y] == 0) {

						if (state == 1) { // 손님이 있는 곳으로
							if (passengers[next_x][next_y].x != -1 && passengers[next_x][next_y].x != -1) { //손님이 있는 곳
								flag = true; //탐색을 1번이라도 성공했다는 flag
								ret = comp(ret, { next_x, next_y }); //문제의 조건에 따라 손님의 우선순위 결정
								dist = cost + 1;
							}
						}
						else { //택시가 손님을 태우고 목적지로
							if (passengers[x][y].x == next_x && passengers[x][y].y == next_y) { //목적지에 도착했을 때
								ret = { next_x, next_y };
								return { ret, cost + 1};
							}
						}
						q.push({ next_x, next_y, cost + 1 });
						visited[next_x][next_y] = true;
					}
				}
			}
		}
		if (flag)
			break;
	}

	return { ret, dist };
}

bool canGo(tInfo &taxi, tInfo to, int state) {  
//state == 1 -> 택시가 손님한테 가는 동작
//state == 2 -> 손님이 목적지에 가는 동작 
	if (to.xy.x == MAX && to.xy.x == MAX)
		return false;
	if (taxi.cost - to.cost >= 0) {
		taxi.xy.x = to.xy.x;
		taxi.xy.y = to.xy.y;

		if (state == 1) // 손님을 태우러 감
			taxi.cost -= to.cost;
		else // 손님을 목적지로 이동 시킨 경우
			taxi.cost += to.cost;
			
		return true;
	}
	return false;
}

int solve(tInfo taxi) {

	while (--M >= 0) {

		tInfo toPassenger = BFS(taxi.xy.x, taxi.xy.y, 1); //1번 손님의 위치 탐색
		if (!canGo(taxi, toPassenger, 1)) //2번 손님으로 이동 가능하다면 택시 정보 갱신
			return -1;

		tInfo toDestination = BFS(taxi.xy.x, taxi.xy.y, 2); //3번 목적지의 거리와 좌표 탐색
		if (!canGo(taxi, toDestination, 2))  // 목적지로 이동 가능하다면 택시 정보 갱신
			return -1;

		passengers[toPassenger.xy.x][toPassenger.xy.y] = { -1,-1 }; //하차 후 손님 좌표의 정보 삭제
	}

	return taxi.cost;
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int x, y, cost;
	
	tInfo taxi;
	
	cin >> N >> M >> cost;

	for (int i = 0; i < N; i++)
		for (int j = 0; j < N; j++) {
			cin >> tMap[i][j];
			passengers[i][j] = { -1,-1 }; //도착지의 정보가 0,0이 될 수 있기 때문에 -1로 초기화
		}
	
	cin >> x >> y;
	taxi = { {x - 1, y - 1}, cost };
	
	for (int i = 0; i < M; i++) {
		int x, y, next_x, next_y;
		cin >> x >> y >> next_x >> next_y;
		passengers[x - 1][y - 1] = { next_x - 1 , next_y - 1}; //출발지와 도착지 매핑
	}

	cout << solve(taxi);
}

 

문제 링크


문제 링크

www.acmicpc.net/problem/19237

 

19237번: 어른 상어

첫 줄에는 N, M, k가 주어진다. (2 ≤ N ≤ 20, 2 ≤ M ≤ N2, 1 ≤ k ≤ 1,000) 그 다음 줄부터 N개의 줄에 걸쳐 격자의 모습이 주어진다. 0은 빈칸이고, 0이 아닌 수 x는 x번 상어가 들어있는 칸을 의미

www.acmicpc.net

 

접근 방법


이 문제 또한 구현 및 시뮬레이션 문제였습니다. 

구현을 위해 두가지 구조체를 사용하였습니다. 

 

1. 상어의 방향의 우선순위를 정하기 위한 sharkInfo 입니다. 상어의 현재 진행 방향을 dir, dir에 따른 방향의 우선 순위를 지정하기위한 priority[dir][i]입니다.

typedef struct sharkInfo {
	int dir;
	vector<vector<int>> priority;
};

 

2. 또한 격자의 상태를 나타내기 위한 mapInfo 입니다. 어떤 상어의 냄새인지를 나타내는 num, 냄새가 사라지기 까지 남은 시간 time, 상어의 실제 존재 유무 exist입니다.

typedef struct mapInfo {
	int num, time;
	bool exist;
};

 

 

 이제 1초 동안 일어나는 일에 대해서 생각해 봐야 합니다. 저는 이러한 과정을 3가지로 나누어 생각했습니다.

1. 상어들이 이동할 다음 좌표를 알아낸다.

2. 1에서 찾은 좌표들로 상어들을 이동시킨다.(이 때 번호가 작은 상어가 번호가 큰 상어를 내 쫒음)

3. 전체의 격자에서 냄새가 사라지기 까지 남은 시간인 time을 갱신한다.

 

1. 한 상어의 좌표를 받아 상어의 번호와 다음 좌표를 반환하는 함수를 구현하였습니다.

일단 현재 냄새가 없는 곳으로 갈 수 있는지 탐색 후 그러한 공간이 없다면 자신의 냄새가 있던 곳을 탐색하게 됩니다.

이 때 sharks는 sharkInfo의 정보들을 담고 있는 전역의 벡터 입니다.

pair<int, pair<int, int>> findNextCoord(int x, int y) {

	int num = map[x][y].num;
	sharkInfo shark = sharks[num];
	int dir = shark.dir;

	int next_x = x;
	int next_y = y;
	
    //냄새가 없는 곳을 탐색
	for (int i = 0; i < 4; i++) {
		int nextdir = shark.priority[dir][i];
		next_x = x + direction[nextdir].first;
		next_y = y + direction[nextdir].second;
		if (canMove(next_x, next_y)) {
			sharks[num].dir = nextdir;
			break;
		}
	}
    //그러한 공간이 없다면 자신의 냄새가 있던 곳을 탐색
	if (!canMove(next_x, next_y)) {
		for (int i = 0; i < 4; i++) {
			int nextdir = shark.priority[dir][i];
			next_x = x + direction[nextdir].first;
			next_y = y + direction[nextdir].second;
			if (inRange(next_x, next_y)) {
				if (map[next_x][next_y].num == num) {
					sharks[num].dir = nextdir;
					break;
				}
			}
		}
	}

	return { num, {next_x, next_y} };
}

 

 2. 이동할 좌표에 어떤 냄새도 없다면 이동, 아니라면 자신과 번호가 같거나 큰 곳인 경우 이동

void moveOne(int num, int next_x, int next_y) {
	
	if (map[next_x][next_y].num > 0) { 
		if (map[next_x][next_y].num >= num) { //쫒아냄
			map[next_x][next_y] = { num, K, true };
		}
	}
	else {
		map[next_x][next_y] = { num, K, true };
	}
}

 

3. 현재 상어가 존재하는 곳을 제외한 나머지 공간에 냄새가 남아있다면 -1을 하고 그러한 시간이 0이 되었다면 해당 좌표에 남아있던 상어의 번호를 삭제

for (int i = 0; i < N; i++) {
   for (int j = 0; j < N; j++) {
    	if (!map[i][j].exist && map[i][j].time > 0) {
      		if (--map[i][j].time == 0)
      			map[i][j] = { 0, 0, false };
      	}
    }
}

 

 이러한 과정이 1초 동안 일어나는 일 입니다. 마지막으로 매 초마다 격자에 1번 상어만 남아있는지 검사를 하게 된다면 문제를 해결할 수 있습니다. 

 

 

소스 코드


#include <iostream>
#include <vector>

using namespace std;

typedef struct mapInfo {
	int num, time;
	bool exist;
};

typedef struct sharkInfo {
	int dir;
	vector<vector<int>> priority;
};

pair<int, int> direction[4] = { {-1,0}, {1,0}, {0,-1}, {0,1} };
mapInfo map[21][21];
vector<sharkInfo> sharks;
int N, M, K;

bool inRange(int x, int y) {
	return (0 <= x && x < N && 0 <= y && y < N);
}

bool canMove(int x, int y) {
	if (inRange(x,y)) 
		if (map[x][y].time == 0) 
			return true;
	return false;
}

pair<int, pair<int, int>> findNextCoord(int x, int y) {

	int num = map[x][y].num;
	sharkInfo shark = sharks[num];
	int dir = shark.dir;

	int next_x = x;
	int next_y = y;

	for (int i = 0; i < 4; i++) {
		int nextdir = shark.priority[dir][i];
		next_x = x + direction[nextdir].first;
		next_y = y + direction[nextdir].second;
		if (canMove(next_x, next_y)) {
			sharks[num].dir = nextdir;
			break;
		}
	}
	if (!canMove(next_x, next_y)) {
		for (int i = 0; i < 4; i++) {
			int nextdir = shark.priority[dir][i];
			next_x = x + direction[nextdir].first;
			next_y = y + direction[nextdir].second;
			if (inRange(next_x, next_y)) {
				if (map[next_x][next_y].num == num) {
					sharks[num].dir = nextdir;
					break;
				}
			}
		}
	}

	return { num, {next_x, next_y} };
}

void moveOne(int num, int next_x, int next_y) {
	
	if (map[next_x][next_y].num > 0) { 
		if (map[next_x][next_y].num >= num) { //쫒아냄
			map[next_x][next_y] = { num, K, true };
		}
	}
	else {
		map[next_x][next_y] = { num, K, true };
	}
}

int solve() {

	int cnt = -1;

	while (++cnt <= 1000) {

		vector<pair<int, pair<int, int>>> numAndCoords;

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (map[i][j].exist) {
					map[i][j].exist = false;
					numAndCoords.push_back(findNextCoord(i,j));
				}
			}
		}

		if (numAndCoords.size() == 1) 
			break;

		for (int i = 0; i < numAndCoords.size(); i++) {
			pair<int, pair<int, int>> elem = numAndCoords[i];
			moveOne(elem.first, elem.second.first, elem.second.second);
		}

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (!map[i][j].exist && map[i][j].time > 0) {
					if (--map[i][j].time == 0)
						map[i][j] = { 0, 0, false };
				}
			}
		}
	}

	return cnt = (cnt > 1000 ? -1 : cnt);
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	cin >> N >> M >> K;

	sharks.resize(M + 1);

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			int num; cin >> num;
			map[i][j].num = num;
			if (num > 0) {
				map[i][j].time = K;
				map[i][j].exist = true;
			}
		}
	}

	for (int i = 1; i <= M; i++) {
		cin >> sharks[i].dir;
		sharks[i].dir--;
	}
	
	for (int i = 1; i <= M; i++) {
		for (int j = 0; j < 4; j++) {
			vector<int> priority;
			for (int k = 0; k < 4; k++) {
				int dir; cin >> dir;
				priority.push_back(dir - 1);
			}
			sharks[i].priority.push_back(priority);
		}
	}

	cout << solve();
}

문제 링크


www.acmicpc.net/problem/20544

 

20544번: 공룡게임

크롬 브라우저 상에서 인터넷 연결이 안될때나, 주소창에 chrome://dino 를 입력하면 공룡 게임을 플레이 할 수 있다.

www.acmicpc.net

 

접근 방법

 


 해당문제는 4차원 DP를 사용하여 해결 할 수 있었습니다. 1차원 좌표계에서 4차원 DP를 생각해내는 것이 쉬운 일은 아니었던 문제였습니다.

 

DP의 1~4차원을 설명하자면

1. 현재 위치

2. 현재 높이(0,1,2) 

3. 과거 높이(0,1,2)

4. 높이가 2인 장애물 포함 여부(0,1)

 

 일단 문제를 처음 접근할 때 현재위치와 현재높이를 포함한 DP를 설계해야겠다고 생각했습니다. 이 후 과거의 높이가 다음의 높이 값에 영향을 줄 수 있는 변수라는 것을 알았습니다. 

예를 들어 현재의 값만 생각하였을 때 현재 상태가 1이라면 다음 상태는 0, 1, 2중 어떤 값이 와도 상관이 없습니다. 하지만 과거의 값이 1이었다면 다음 값은 0으로만 갈 수 있게 됩니다.

 

 또한 [현재위치][현재높이][과거높이]가 중복되는 값이 나오더라도 장애물 2를 포함해서 오지 않았다면 다른 경우의 수가 된다는 것을 떠올릴 수 있었습니다. 즉 DP에 장애물 2를 포함했는지의 여부를 나타내는 차원을 추가하였습니다.

 

소스 코드


#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;
long long dp[1001][3][3][2];
long long mod = 1000000007;
int N;

long long solve(int now, int state, bool flag, int laststate) {

	long long &ref = dp[now][state][laststate][flag];

	if (now == N - 1) {
		if (flag)
			return ref = 1;
		else
			return ref;
	}	
	if (ref != 0)
		return ref;
	
	if (state == 0) {
		ref += solve(now + 1, 0, flag, state) % mod;
		ref += solve(now + 1, 1, flag, state) % mod;
		ref += solve(now + 1, 2, true, state) % mod;
	}
	else if (state == 1) {
		if (laststate == 0) {
			ref += solve(now + 1, 0, flag, state) % mod;
			ref += solve(now + 1, 1, flag, state) % mod;
			ref += solve(now + 1, 2, true, state) % mod;
		}
		else {
			ref += solve(now + 1, 0, flag, state) % mod;
		}
	}
	else if(state == 2){
		if (laststate == 0) {
			ref += solve(now + 1, 0, flag, state) % mod;
			ref += solve(now + 1, 1, flag, state) % mod;
		}
		else {
			ref += solve(now + 1, 0, flag, state) % mod;
		}
	}
	return ref = ref % mod;
}

int main() {

	ios_base::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	cin >> N;

	cout << solve(0, 0, false, 0) << "\n";

}

 

문제 링크


www.acmicpc.net/problem/20056

 

20056번: 마법사 상어와 파이어볼

첫째 줄에 N, M, K가 주어진다. 둘째 줄부터 M개의 줄에 파이어볼의 정보가 한 줄에 하나씩 주어진다. 파이어볼의 정보는 다섯 정수 ri, ci, mi, si, di로 이루어져 있다. 서로 다른 두 파이어볼의 위치

www.acmicpc.net

 

접근 방법


문제에 제시된 대로 구현을 하면 됩니다.

큰 범주로 나누어 본다면

 

1. 파이어볼이 자신의 정보를 가지고 이동

2. 격자 정보에 따라 파이어볼을 합치고 분해하여 파이어볼의 정보 갱신

 

1. 파이어볼의 이동은 2가지 동작으로 구분 지었습니다.

 

1-1. 파이어볼을 이동시키기 전 격자를 스캔하며 파이어볼의 정보를 vector<fire> fires에 저장함과 동시에 격자에서 제거를 합니다.

 제거를 하는 이유는 fires의 정보를 가지고 파이어볼들을 재배치를 하기 위함입니다. 또한 미리 fires에 정보를 저장하는 이유는 격자에서 바로바로 옮기게 되면 현재 보고 있는 불이 이동을 했는지 아닌지에 대한 검사를 하는 과정이 더 요구 될 것이기 때문입니다.

void moveAll() {

	vector<fire> fires;

	for (int i = 0; i < N; i++)  //격자에서 불을 제거하며 정보를 벡터에 담는다.
		for (int j = 0; j < N; j++) 
			while(!map[i][j].empty()){
				fire elem = map[i][j].back();
				map[i][j].pop_back();
				fires.push_back(elem);
			}

	for (int i = 0; i < fires.size(); i++)
		moveOne(fires[i]);
}

 

1-2. 이제 1-1에서 fires에 저장한 정보를 바탕으로 파이어볼을 하나씩 이동시킬 것입니다. 

 이동 시킬 때의 조건으로는 격자에서 파이어볼이 순회하는 구조이므로 좌표가 N - 1을 벗어난다면 0으로, 0을 벗어난다면  N - 1로 이동시켜 주어야 합니다. 

 또한 파이어볼을 방향으로 속도의 횟수 만큼 한 칸씩 이동시켜도 시간초과에는 걸리지 않으나 한 파이어볼에 대해 최대 1000번의 반복문이 연산이 될 수 있으므로 주기를 검사해 반복되는 연산을 최소화 시켜주어야 합니다.

 

만약 4X4격자에서 S지점에서 오른쪽으로 11칸을 진행한다고 하면 아래 그림과 같습니다.

여기서 주기는 4가 될 것이며 주기만큼 이동하면 자신의 원래 위치로 오게 될 것 입니다. 즉 4, 8일 때 출발위치와 같아질 것입니다. 그렇다면 남은거리는 leftDistance = (totalDistance) % N 이 될 것이며 남은 거리만큼 범위를 신경써서 이동시키면 됩니다.

void moveOne(fire elem) {

	int next_x = elem.x + direction[elem.d].first * elem.s;
	int next_y = elem.y + direction[elem.d].second * elem.s;

	if (!inRange(next_x, next_y)) {
		
		next_x = elem.x;
		next_y = elem.y;
		int leftDist = elem.s % N;

		while (--leftDist >= 0) {
			next_x += direction[elem.d].first;
			next_y += direction[elem.d].second;
			next_x = (next_x > N - 1) ? 0 : next_x;
			next_x = (next_x < 0) ? N - 1 : next_x;
			next_y = (next_y > N - 1) ? 0 : next_y;
			next_y = (next_y < 0) ? N - 1 : next_y;
		}
	}
	map[next_x][next_y].push_back({ next_x, next_y, elem.m, elem.s, elem.d });
}

 

 

2. 파이어볼의 이동 이후 격자를 스캔하며 2개 이상의 파이어볼이 있는 곳에 대해 합과 분해를 합니다.

void processing() { //합과 분해

	for (int i = 0; i < N; i++) 
		for (int j = 0; j < N; j++) 
			if (map[i][j].size() > 1) {
				fire fireball = composition(map[i][j]); //합쳐진 파이어볼의 정보
				decomposition(fireball);
			}
}

 

2-1. 합의 과정에 있어서 방향을 설정하는 것은 아래와 같은 코드로 구현을 하였습니다.

if (tmp == ball.d % 2)
	dir *= 1;
else
	dir *= 0;

 tmp는 이전 방향의 정보이며 현재 방향과 일치하는지 비교를 합니다. dir은 1로 초기화 되어있으며 한 번이라도 방향이 이전과 달라진다면 dir은 0이 될 것이며 이 후에는 어떤 수를 dir에 곱해주어도 dir은 0을 유지합니다.

 

소스 코드


#include <iostream>
#include <vector>

using namespace std;

typedef struct fire {
	int x, y, m, s, d;
};

pair <int, int> direction[8] = { {-1,0}, {-1,1}, {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1} };
vector<fire> map[52][52];
int N, M, K;

bool inRange(int x, int y) {
	return (0 <= x && x < N && 0 <= y && y < N);
}

int messSum() {

	int sum = 0;

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			vector<fire> area = map[i][j];
			for (fire &one : area) 
				sum += one.m;
		}
	}
	return sum;
}

void decomposition(fire &fireball) { //분해

	//방향 처리를 위한 숫자 전환
	//모든 방향이 홀이거나 짝수이면 dir은 0 아니라면 1
	int d = (fireball.d == 0) ? 1 : 0; 

	for (int i = 0; i < 4; i++) 
		if(fireball.m > 0)
			map[fireball.x][fireball.y].push_back({ fireball.x, fireball.y, fireball.m, fireball.s, d + (i * 2) });
}

fire composition(vector<fire> &area) { //합

	int dir = 1;
	int cnt = area.size();
	int mSum = 0 , sSum = 0;
	int tmp = area.back().d % 2;
	int x = area.back().x;
	int y = area.back().y;
	
	while (!area.empty()) {
		
		fire ball = area.back();
		area.pop_back();

		mSum += ball.m;
		sSum += ball.s;

		if (tmp == ball.d % 2)
			dir *= 1;
		else
			dir *= 0;
		tmp = ball.d % 2;
	}
	//모든 방향이 홀이거나 짝수이면 dir은 1 반환, 아니라면 0반환
	return { x, y, mSum / 5, sSum / cnt, dir };
}

void processing() { //합과 분해

	for (int i = 0; i < N; i++) 
		for (int j = 0; j < N; j++) 
			if (map[i][j].size() > 1) {
				fire fireball = composition(map[i][j]); //합쳐진 파이어볼의 정보
				decomposition(fireball);
			}
}

void moveOne(fire elem) {

	int next_x = elem.x + direction[elem.d].first * elem.s;
	int next_y = elem.y + direction[elem.d].second * elem.s;

	if (!inRange(next_x, next_y)) {
		
		next_x = elem.x;
		next_y = elem.y;
		int leftDist = elem.s % N;

		while (--leftDist >= 0) {
			next_x += direction[elem.d].first;
			next_y += direction[elem.d].second;
			next_x = (next_x > N - 1) ? 0 : next_x;
			next_x = (next_x < 0) ? N - 1 : next_x;
			next_y = (next_y > N - 1) ? 0 : next_y;
			next_y = (next_y < 0) ? N - 1 : next_y;
		}
	}
	map[next_x][next_y].push_back({ next_x, next_y, elem.m, elem.s, elem.d });
}

void moveAll() {

	vector<fire> fires;

	for (int i = 0; i < N; i++)  //맵에서 불을 제거하며 정보를 벡터에 담는다.
		for (int j = 0; j < N; j++) 
			while(!map[i][j].empty()){
				fire elem = map[i][j].back();
				map[i][j].pop_back();
				fires.push_back(elem);
			}

	for (int i = 0; i < fires.size(); i++)
		moveOne(fires[i]);
}

int solve() {

	while (--K >= 0) {
		moveAll();
		processing();
	}
	return messSum();
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	cin >> N >> M >> K;

	for (int i = 0; i < M; i++) {
		int r, c, m, s, d;
		cin >> r >> c >> m >> s >> d;
		map[r - 1][c - 1].push_back({ r - 1,c - 1,m,s,d });
	}
	cout << solve();
}

문제 링크


www.acmicpc.net/problem/20057

 

20057번: 마법사 상어와 토네이도

마법사 상어가 토네이도를 배웠고, 오늘은 토네이도를 크기가 N×N인 격자로 나누어진 모래밭에서 연습하려고 한다. 위치 (r, c)는 격자의 r행 c열을 의미하고, A[r][c]는 (r, c)에 있는 모래의 양을

www.acmicpc.net

 

접근 방법


 시뮬레이션이나 구현 류의 문제들은 초기 설계를 어떻게 하느냐에 따라 코드 길이가 차이가 많이 날 수 있으니 초반 설계를 잘하는 것이 중요하다. 코드가 길어진다는 것은 구현이 복잡해질 가능성이 높다는 의미이다.

 

1. 상, 하, 좌, 우 방향에 따른 상대적인 좌표와 그 비율을 미리 저장한다.

typedef struct info{
	int x, y, cost;
};

vector<vector<info>> infoV;

void init() {
	// left, right는 요소의 2번째 인자의 부호를 바꿔주면 되고, up, down은 요소의 1번째 인자의 부호를 전환
	// 알파의 비율 부분은 모든 요소 처리 이후 따로 계산 
	infoV.push_back({ {-1,0,1}, {-1,-1,7}, {-2,-1,2}, {-1,-2,10}, {1,0,1}, {1,-1,7}, {2,-1,2}, {1,-2,10}, {0,-3,5} }); //left
	infoV.push_back({ {0,-1,1}, {1,-1,7}, {1,-2,2}, {2,-1,10}, {0,1,1}, {1,1,7}, {1,2,2}, {2,1,10}, {3,0,5} });  //down
	infoV.push_back({ {-1,0,1}, {-1,1,7}, {-2,1,2}, {-1,2,10}, {1,0,1}, {1,1,7}, {2,1,2}, {1,2,10}, {0,3,5} });   //right
	infoV.push_back({ {0,-1,1}, {-1,-1,7}, {-1,-2,2}, {-2,-1,10}, {0,1,1}, {-1,1,7}, {-1,2,2}, {-2,1,10}, {-3,0,5} }); // up
}

 

2. 문제 풀이가 종료 될 때까지 방향의 전환 횟수는 2*N - 1번 일어난다.

 현재 방향의 전환 횟수를 iter라고 하고, 좌,하,우,상의 방향을 순서대로 0,1,2,3이라고 하였을 때

(방향의 순서는 문제에서 제시된 대로 정했다.)

 (1) 현재진행방향 = (iter)%4

 (2) 진행방향크기 = (iter)%2 + 1(단, 마지막 진행방향에선 (iter)%2)

이러한 규칙을 찾아 낼 수 있다.

 

3. 위의 규칙에 따라 iter를 1씩 증가시키며 한 방향으로 진행하는 처리를 할 수 있다.

 

4.  3의 과정에서 한 칸씩 옮겨가는 과정을 처리하여 값을 얻어낸다.

 

소스 코드


#include <iostream>
#include <vector>

using namespace std;

typedef struct info{
	int x, y, cost;
};

vector<vector<info>> infoV;
pair<int, int> direction[4] = { {0,-1}, {1,0}, {0,1}, {-1,0} };
int map[1000][1000];
int N;

void init() {
	// left, right는 요소의 2번째 인자의 부호를 바꿔주면 되고, up, down은 요소의 1번째 인자의 부호를 전환
	// 알파의 비율 부분은 모든 요소 처리 이후 따로 계산 
	infoV.push_back({ {-1,0,1}, {-1,-1,7}, {-2,-1,2}, {-1,-2,10}, {1,0,1}, {1,-1,7}, {2,-1,2}, {1,-2,10}, {0,-3,5} }); //left
	infoV.push_back({ {0,-1,1}, {1,-1,7}, {1,-2,2}, {2,-1,10}, {0,1,1}, {1,1,7}, {1,2,2}, {2,1,10}, {3,0,5} });  //down
	infoV.push_back({ {-1,0,1}, {-1,1,7}, {-2,1,2}, {-1,2,10}, {1,0,1}, {1,1,7}, {2,1,2}, {1,2,10}, {0,3,5} });   //right
	infoV.push_back({ {0,-1,1}, {-1,-1,7}, {-1,-2,2}, {-2,-1,10}, {0,1,1}, {-1,1,7}, {-1,2,2}, {-2,1,10}, {-3,0,5} }); // up
}

bool inRange(int x, int y) {
	return (0 <= x && x < N && 0 <= y && y < N);
}

int onePoint(int x, int y, int dir) { 
// 한 지점에 대한 처리
	vector<info> spread = infoV[dir];
	int spreadSand = 0; // 지금까지 퍼져나간 모래, 알파지점을 계산하기 위한 값
	int outSand = 0;    // 범위 밖으로 나간 모래 -> 우리가 원하는 값
	int target_x = x + direction[dir].first;  //문제의 y지점의 행
	int target_y = y + direction[dir].second; //문제의 y지점의 열

	for (int i = 0; i < spread.size(); i++) {
		
		int cost = spread[i].cost * 0.01 * map[target_x][target_y];
		int next_x = x + spread[i].x;
		int next_y = y + spread[i].y;
		spreadSand += cost; //알파 지점의 값을 알기위해 퍼져나간 모래를 더함

		if (inRange(next_x, next_y)) //범위 내에 있다면 모래를 더해줌
			map[next_x][next_y] += cost; 
		else    // 범위 내에 없다면 밖으로 나간 모래 값을 더해줌
			outSand += cost;
	}
	
    // 알파 지점 처리
    //알파 지점은 현재 위치에서 진행방향으로 두 칸 떨어짐
	int next_x = x + direction[dir].first * 2; 
	int next_y = y + direction[dir].second * 2; 
    // 지금까지 퍼져나간 모래의 값을 y지점에서 빼준다.
	int cost = map[target_x][target_y] - spreadSand;
	map[target_x][target_y] = 0;

	if (inRange(next_x, next_y))
		map[next_x][next_y] += cost;
	else
		outSand += cost;
	
	return outSand;
}

info oneDirection(int dir, int len, int x, int y) {
//한 진행방향에 대한 처리
	int sum = 0;

	for (int i = 0; i < len; i++) {
		sum += onePoint(x, y, dir);
		x += direction[dir].first;
		y += direction[dir].second;
	}
	return { x,y,sum };
}

int solve() {

	int ans = 0;
	int iter = -1;
	int x = N / 2;
	int y = N / 2;

	while (++iter < N * 2 - 1) { //방향 전환 횟수
		int len = (iter == (N - 1) * 2) ? iter / 2 : iter / 2 + 1; //마지막 전환에선 len의 크기 증가 x
		info result = oneDirection(iter % 4, len, x, y);
		x = result.x;
		y = result.y;
		ans += result.cost;
	}

	return ans;
}

int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	init();

	cin >> N;

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

	cout << solve();
}

개발 환경 : VS2017

질문, 지적 환영합니다!

+ Recent posts