반응형

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

 

14502번: 연구소

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다. 연구소는 크

www.acmicpc.net

1.문제설명

7 7
2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

0은 빈공간이고 1은 벽이고 2는 바이러스다.

바이러스는 상하좌우로 퍼질수 있고 벽은 통과할 수 없다.

벽을 3개 설치해서 바이러스가 퍼지지 않는 빈공간(0)의 최대 개수를 구해야 하는 문제다.

 

 

2.접근방법[알고리즘]

1.추가로 벽(1)을 세개 설치해야한다.

2.벽을 세개 설치 한 뒤 바이러스가 퍼진 결과를 구한다.

3. 그 결과에서 0의 개수를 구해준다.

4. 0의 개수를 저장해주며 벽을 다르게 설치할 때마다 0의 개수를 구하고 최댓값을 출력한다.

 

 

이렇게 풀기 위해서

벽을 세개 설치 하는 걸 DFS함수로 구현했다.

벽을 세개 설치하면 그 때 바이러스를 퍼트린 후 0의 갯수를 리턴한다.

DFS를 돌면서 ans변수에 최댓값을 저장해준다.

void DFS(int x, int y, int L) {
	if (L == 3) {
		//바이러스 BFS 돌고 0 갯수 새기
		ans = max(ans, Count());
	}
	else {
		// 벽 만들기 백트래킹
		for (int i = x; i < n; i++, y=0) {
			for (int j = y; j < m; j++) {
				if (arr[i][j] == 0) {
					arr[i][j] = 1;
					DFS(i, j, L + 1);
					arr[i][j] = 0;
				}
			}
		}
	}
}

2중 for문을 왜 저렇게 쓰는지 이해가 안가면

https://dingcoding.tistory.com/85

 

백준 15684번 : 사다리 조작 - DFS, 백트래킹, 가지치기의 중요성

https://www.acmicpc.net/problem/15684 15684번: 사다리 조작 사다리 게임은 N개의 세로선과 M개의 가로선으로 이루어져 있다. 인접한 세로선 사이에는 가로선을 놓을 수 있는데, 각각의 세로선마다 가로선

dingcoding.tistory.com

위 글의 3.문제풀이코드 부분에 설명을 보면 좋다.

 

 

 

 

벽을 설치한뒤 0의 갯수를 구하는 Count 함수를 구현했다.

int Count() {
	BFS();
	int cnt = 0;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			if (arr[i][j] == 0) cnt++;
			else if (arr[i][j] == 2) {
				//arr배열을 재사용하기위해 BFS 돌아서 바이러스가 퍼진 영역을 
				//다시 0으로 만들어준다.
				arr[i][j] = 0;
			}
		}
	}
	//바이러스 초기화 arr 원상태로 만들어줌
	for (int i = 0; i < virus.size(); i++) 
		arr[virus[i].first][virus[i].second] = 2;
	return cnt;
}

 

Count를 다 하고 나면 arr배열에 초기 입력값을 그대로 저장해준다.

 

 

 

 

3.문제풀이코드 C++

#include <bits/stdc++.h>
using namespace std;

int n, m, arr[10][10], ans;
int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

vector<pair<int, int> > virus;
queue<pair<int, int > > Q;

// 바이러스 퍼트리는 함수 
void BFS() {
	for (int i = 0; i < virus.size(); i++) {
		Q.push({ virus[i].first, virus[i].second });
	}
	while (!Q.empty()) {
		int x = Q.front().first;
		int y = Q.front().second;
		Q.pop();
		for (int i = 0; i < 4; i++) {
			int nx = x + dx[i];
			int ny = y + dy[i];
			if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
			if (arr[nx][ny] == 0) {
				//바이러스가 퍼질 수 있는 곳이면 바이러스로 만들어준다.
				arr[nx][ny] = 2;
				Q.push({ nx,ny });
			}
		}
	}
}

//0 갯수 세는 함수 
int Count() {
	BFS();
	int cnt = 0;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			if (arr[i][j] == 0) cnt++;
			else if (arr[i][j] == 2) {
				//arr배열을 재사용하기위해 BFS 돌아서 바이러스가 퍼진 영역을 
				//다시 0으로 만들어준다.
				arr[i][j] = 0;
			}
		}
	}
	//바이러스 초기화 arr 원상태로 만들어줌
	for (int i = 0; i < virus.size(); i++) 
		arr[virus[i].first][virus[i].second] = 2;
	return cnt;
}

void DFS(int x, int y, int L) {
	if (L == 3) {
		//바이러스 BFS 돌고 0 갯수 새기
		ans = max(ans, Count());
	}
	else {
		// 벽 만들기 백트래킹
		for (int i = x; i < n; i++, y=0) {
			for (int j = y; j < m; j++) {
				if (arr[i][j] == 0) {
					arr[i][j] = 1;
					DFS(i, j, L + 1);
					arr[i][j] = 0;
				}
			}
		}
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			cin >> arr[i][j];
			if (arr[i][j] == 2) {
				virus.push_back({ i,j });
			}
		}
	}
	DFS(0, 0, 0);
	cout << ans << '\n';
}

14502 연구소 문제 시간, 메모리 

문제풀이후기

다중 for문을 사용해 벽을 세우는 방법도 있다.

그래도 위 코드처럼 DFS를 이용해서 벽을 세우고 백트래킹해주면

더 간결한 것 같다.

 

반응형
반응형

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

 

15653번: 구슬 탈출 4

첫 번째 줄에는 보드의 세로, 가로 크기를 의미하는 두 정수 N, M (3 ≤ N, M ≤ 10)이 주어진다. 다음 N개의 줄에 보드의 모양을 나타내는 길이 M의 문자열이 주어진다. 이 문자열은 '.', '#', 'O', 'R', 'B'

www.acmicpc.net

 

 

기존 구슬 탈출 문제 풀이 및 설명

https://dingcoding.tistory.com/86

 

백준 13459번 : 구슬 탈출 - BFS C++ 문제풀이코드

https://www.acmicpc.net/problem/13459 13459번: 구슬 탈출 첫 번째 줄에는 보드의 세로, 가로 크기를 의미하는 두 정수 N, M (3 ≤ N, M ≤ 10)이 주어진다. 다음 N개의 줄에 보드의 모양을 나타내는 길이 M의..

dingcoding.tistory.com

 

BFS 제한 조건만 조금 바꿔주면 동일한 문제다.

 

C++ 문제풀이 코드

#include <bits/stdc++.h>
using namespace std;

int n, m;
char arr[15][15];
bool ch[15][15][15][15], isend;
int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

struct Ball {
	int rx, ry, bx, by, trial;
	Ball(int a, int b, int c, int d, int e) {
		rx = a;
		ry = b;
		bx = c;
		by = d;
		trial = e;
	}
};


void move(int &nrx, int &nry, int dx, int dy, int &cnt) {
	while (1) {
		if (arr[nrx][nry] == '#') {
			nrx -= dx;
			nry -= dy;
			cnt--;
			break;
		}
		if (arr[nrx][nry] == 'O') {
			break;
		}
		nrx += dx;
		nry + dy;
		cnt++;
	}
}



int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	int rx1, ry1, bx1, by1;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> arr[i][j];
			if (arr[i][j] == 'R') {
				rx1 = i;
				ry1 = j;
			}
			else if (arr[i][j] == 'B') {
				bx1 = i;
				by1 = j;
			}
		}
	}

	queue<Ball> Q;
	ch[rx1][ry1][bx1][by1] = 1;
	Q.push(Ball(rx1, ry1, bx1, by1, 0));


	while (!Q.empty()) {
		int rx = Q.front().rx;
		int ry = Q.front().ry;
		int bx = Q.front().bx;
		int by = Q.front().by;
		int trial = Q.front().trial;
		Q.pop();

		for (int i = 0; i < 4; i++) {
			// 다음 빨간공과 다음 파란공의 좌표, cnt는 이동 거리 
			int nrx = rx, nry = ry, rcnt = 0;
			int nbx = bx, nby = by, bcnt = 0;

			//공 기울이기 
			move(nrx, nry, dx[i], dy[i], rcnt);
			move(nbx, nby, dx[i], dy[i], bcnt);

			
			if (arr[nbx][nby] == 'O') continue;
			else if (arr[nrx][nry] == 'O') {
				cout << trial + 1;
				isend = true;
				return 0;
			}
			else {
				//기울였는데 공이 겹친 경우 더 멀리서 온 공을 덜 이동시켜야된다
				if (nrx == nbx && nry == nby) {
					if (rcnt > bcnt) {
						nrx -= dx[i];
						nry -= dy[i];
					}
					else {
						nbx -= dx[i];
						nby -= dy[i];
					}
				}

				if (ch[nrx][nry][nbx][nby] == 0) {
					ch[nrx][nry][nbx][nby] = 1;
					Q.push(Ball(nrx, nry, nbx, nby, trial + 1));
				}
			}
		}
	}

	if (!isend) {
		cout << -1;
	}


}
반응형
반응형

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

 

13913번: 숨바꼭질 4

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일

www.acmicpc.net

1.문제설명

수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다.

수빈이의 위치가 X일 때 걷는다면 1초 후에 X-1 또는 X+1로 이동하게 된다.

순간이동을 하는 경우에는 1초 후에 2*X의 위치로 이동하게 된다.

수빈이가 동생을 찾는 최소 시간과 경로(여러가지 중 하나)를 구해야한다.

 

2.접근방법[알고리즘]

수빈이가 동생을 찾는 최단 경로를 구하기 위해 BFS를 활용해주면 된다.

수빈이가 방문하는 점에 대해서 ch배열에 저장해주고, ch배열에는 이전 노드의 값을 저장해준다.

이전 노드의 값을 저장해주면 후에 재귀함수를 돌면서 경로를 쉽게 구해줄 수 있다.

void DFS(int x) {
	if (ch[x] == -1) {
		cout << x << ' ';
	}
	else {
		DFS(ch[x]);
		cout << x << ' ';
	}
}

ch[17] == 16

ch[16] == 8

ch[8] == 4

ch[4] == 5

 

숨바꼭질 메모리 초과 , 시간초과 이유

단 위 문제에서 노드를 0번부터 쓰기 때문에 ch[1] = 0 이된다. 

그렇기 떄문에 방문하지 않은 점인지 확인할 때

ch[x]==0 을 확인하는 게 아니라

0이 아닌 사용하지 않는 다른 값으로 초기화해주고 그 값을 통해 확인해주어야한다.

아래 코드에서는 ch[x] = -10 으로 초기화 해놓고 ch[x] == -10 이라면 방문하지 않은 점으로 확인해주었다.

 

만약 0으로 확인해주면

ch[1] == 0 인데, 2번 노드에서 BFS를 할 때

ch[1] == 2로 다시 변경된다. 이러면 ch[2]==1 이어서

위 DFS코드에서 1과 2를 무한 번 돈다.

무한 반복해서 시간초과나 메모리초과가 발생하게 된다.

 

 

 

3.문제풀이코드 C++

#include <bits/stdc++.h>
using namespace std;

int n, k;
// ch에 이전 방문지 저장
int ch[100001];

//방문 노드 출력 함수
void DFS(int x) {
	if (ch[x] == -1) {
		cout << x << ' ';
	}
	else {
		DFS(ch[x]);
		cout << x << ' ';
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
    
	for (int i = 0; i < 100001; i++) {
		// 0으로부터 움직이는 경우도 있기 때문에 초기화
        // ch[i] == -10이면 아직 방문 안한 곳
		ch[i] = -10;
	}


	cin >> n >> k;
	
	//위치, 시간 저장
	queue<pair<int, int> > Q;

	Q.push({ n,0 });
	ch[n] = -1;

	while (!Q.empty()) {
		int x = Q.front().first;
		int t = Q.front().second;
		Q.pop();
        
		if (x == k) {
			cout << t <<'\n';
			DFS(k);
			return 0;
		}

		if (x - 1 >= 0 && ch[x-1] == -10) {
			ch[x - 1] = x;
			Q.push({ x - 1, t + 1 });
		}
		if (x + 1 <= 100000 && ch[x + 1] == -10) {
			ch[x + 1] = x;
			Q.push({ x + 1,t + 1 });
		}

		if (2 * x <= 100000 && ch[2 * x] == -10) {
			ch[2 * x] = x;
			Q.push({ 2 * x,t + 1 });
		}

	}

}

백준 13913

문제풀이후기

check배열에 이전 방문한 노드를 저장해 줄 때 노드를 0번도 사용 한다면 

방문한 노드인지 비교할 때 초기화에 대해서 한 번 더 생각해주어야 한다.

반응형
반응형

비주얼스튜디오 VS LNK1104 오류 해결방법

LNK1104

비주얼스튜디오에서 C++로 가끔 컴파일을 하다보면 저런 오류가 발생한다.

컴파일을 연속적으로 할 때 이전에 실행된 프로그램이 비정상적으로 종료되어

아직 메모리상 남아있는 상태기 때문에 발생하는 오류이다.

열심히 코딩하다 뜨면 열받는다.

 

LNK1104 오류 해결 과정

1. CMD를 실행

2. TASKLIST 를 적어준다. 그러면 현재 작업중인 Task의 목록이 나열된다.

3. TASKLIST에서 컴파일하던 exe 파일의 제목의 옆에 있는 PID를 찾는다.

4. TASKKILL /F /PID (찾은 PID 숫자)

5. 비주얼스튜디오로 다시 컴파일이 가능하다.

 

스크린샷 첨부한 해결 과정

1. CMD를 실행한다.

2. TASKLIST 를 적어준다. 그러면 현재 작업중인 Task의 목록이 나열된다.

LNK1104
PID

3. 컴파일하던 exe 파일의 제목을 찾아 PID를 기억한다. Project1.exe 파일의 PID는 16964다.

 

4. TASKKILL /F /PID 16964(종료하고자하는 PID), 를 해준다.

각자 컴퓨터마다, 프로그램마다 PID가 다르므로 PID를 잘 적어야한다.

TASKKILL /F /PID

 

5. 이제 다시 비주얼스튜디오를 사용하면 된다.

 

 

 

etc..

TASKKILL 명령어 매개변수 설명

cmd창에 TASKKILL /? 를 치면 나온다.

TASKKILL 매개변수

/F는 강제종료 /PID는 프로세스 ID를 의미한다.

반응형
반응형

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

 

13459번: 구슬 탈출

첫 번째 줄에는 보드의 세로, 가로 크기를 의미하는 두 정수 N, M (3 ≤ N, M ≤ 10)이 주어진다. 다음 N개의 줄에 보드의 모양을 나타내는 길이 M의 문자열이 주어진다. 이 문자열은 '.', '#', 'O', 'R', 'B'

www.acmicpc.net

1. 문제설명

구슬이 있는 판을 상하좌우로 기울일 수 있고,10번의 기회가 있다.

빨간색 구슬만 'O'를 만나는 경우가 있다면 1을 출력하고 없으면 0을 출력한다.

빨간색구슬이 'O'를 만날 때 파란색 구슬도 'O'를 만나는 경우는 제외한다.

 

2. 알고리즘

1. 구슬 판을 기울이는 함수를 구현해야 한다. dx, dy 방향으로 움직이고 'O'이나 '#'을 만나면 멈춘다.

2. 4방향으로 구슬판을 기울이고 기울인 결과가 이전에 이미 했던 경우가 아니라면 큐에 넣어준다.

3. 기울인 결과를 check 해주는 배열에 넣어준다.

4. BFS를 10번 돌 때 까지 빨간 공이 'O를 만나는 경우가 존재하지 않으면 BFS를 종료하고 '0'을 출력한다.

 

 

 

 

3.문제풀이코드 C++

#include <bits/stdc++.h>
using namespace std;
int n, m, red_x, red_y, blue_x, blue_y, cnt = 0;

char board[15][15];
int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };
bool vis[15][15][15][15], isend;

struct Edge {
	//c는 BFS 시행 횟수 저장 
	int a, b, c;
	Edge(int x, int y, int z) {
		a = x;
		b = y;
		c = z;
	}

};

//끝까지 이동
void move(int &nrx, int &nry, int dx, int dy, int &rcnt) {
	while (1) {
		if (board[nrx][nry] == '#') { //벽에 무조건 걸린다
			nrx -= dx;
			nry -= dy;
			rcnt--;
			break;
		}
		if (board[nrx][nry] == 'O') {
			break;
		}
		nrx += dx;
		nry += dy;
		rcnt++;
	}
}

//
//void print(int nrx, int nry, int nbx, int nby, int count) {
//	cout << "----------------\n";
//
//	for (int i = 1; i <= n; i++) {
//		for (int j = 1; j <= m; j++) {
//			if (i == nrx && j == nry) cout << 'R';
//			else if (i == nbx && j == nby) cout << "B";
//			else cout << board[i][j];
//		}
//		cout << '\n';
//	}
//	cout << "BFS CNT : "<<count << "-------------\n\n";
//}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	//Red//Blue 순
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> board[i][j];
			if (board[i][j] == 'R') {
				board[i][j] = '.';
				red_x = i;
				red_y = j;
			}
			else if (board[i][j] == 'B') {
				board[i][j] = '.';
				blue_x = i;
				blue_y = j;
			}
		}
	}
	queue<Edge> Q;
	Q.push(Edge(red_x,red_y,0));
	Q.push(Edge(blue_x,blue_y,0));
	vis[red_x][red_y][blue_x][blue_y] = 1;

	while (!Q.empty()) {
		int rx = Q.front().a;
		int ry = Q.front().b;
		Q.pop();
		int bx = Q.front().a;
		int by = Q.front().b;
		int count = Q.front().c;
		Q.pop();


		//print(rx, ry, bx, by, count);
		if (count >= 10) break;
		

		for (int i = 0; i < 4; i++) {
			int rcnt = 0, bcnt = 0;
			int nrx = rx;
			int nry = ry;
			int nbx = bx;
			int nby = by;
			move(nrx, nry, dx[i], dy[i], rcnt);
			move(nbx, nby, dx[i], dy[i], bcnt);

			if (board[nbx][nby] == 'O') {
				continue;
			}
			else if (board[nrx][nry] == 'O') {
				//print(nrx, nry, nbx, nby, count + 1);
				cout << 1 << '\n';
				isend = true;
				return 0;
			}
			else {
				if (nbx == nrx && nry == nby) {
					if (rcnt > bcnt) {
						nrx -= dx[i];
						nry -= dy[i];
					}
					else {
						nbx -= dx[i];
						nby -= dy[i];
					}
				}
				if (vis[nrx][nry][nbx][nby] == 0) {
					vis[nrx][nry][nbx][nby] = 1;
					Q.push(Edge(nrx,nry,count+1));
					Q.push(Edge(nbx,nby,count+1));
				}
			}
		}
	}

	if (!isend) {
		cout << 0;
	}
	return 0;
}

백준 13459번 구슬탈출&nbsp;

 

주석 부분을 해제하면 이동 과정을 출력해볼 수 있다.

 

문제푸는데 헷갈려서 넣어봤다.

 

구슬 이동 출력

백준 13459번 구슬탈출 구슬이동과정
백준 13459번 구슬탈출 구슬이동과정

 

 

문제풀이후기

아무래도 그동안 풀던 문제에 비해 코드 길이는 길어졌다.

코드를 함수를 잘 만들어서 중복되는 부분을 줄일수록 코드 구성을 판단하기 쉬워진다.

복잡하지만 결국 4방향에 대해 너비우선탐색을 하여

문제조건에 알맞는 결과가 있다면 종료해주는 문제였다.

다만 탐색하는 부분에 대하여 조건에 맞춰 코드를 작성해야할 필요가 있다.

반응형
반응형

 

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

 

15684번: 사다리 조작

사다리 게임은 N개의 세로선과 M개의 가로선으로 이루어져 있다. 인접한 세로선 사이에는 가로선을 놓을 수 있는데, 각각의 세로선마다 가로선을 놓을 수 있는 위치의 개수는 H이고, 모든 세로선

www.acmicpc.net

1.문제설명

15864번 사다리 조작 문제 설명

N*H의 사다리가 주어진다.

사다리게임의 결과로 모든 숫자에 대해 X번은 X를 갖도록

사다리 갯수를 추가해 조작해주어야 한다.

단 필요한 사다리 갯수가 3개를 초과하면 -1을 출력해준다.

 

 

 

2.접근 방법[알고리즘]

가로에 사다리가 없는 부분에 대하여 사다리를 추가해주었을 때 문제 조건을 맞는지 확인하면서

백트래킹을 돌아보면 된다. 이전의 백준 스도쿠 문제와 유사하다.

 

https://dingcoding.tistory.com/83

 

백준 2580번: 스도쿠 - DFS, 백트래킹 C++

https://www.acmicpc.net/problem/2580 2580번: 스도쿠 스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로,

dingcoding.tistory.com

다만 백트래킹을 돌면서 확인해야할 조건은 가로선이 연속되면 안된다.

그리고 가로선을 하나씩 선택해주면서 갯수가 3개를 넘어간다면 return 조건을 추가해

3개이상 도는 경우를 가지치기 해준다.

 

또한 가로선을 선택하는 것에 대하여 이전에 선택했던 걸 돌면서 다시 선택하면

불필요하게 중복적인 계산을 하므로 사다리를 선택하는 방법은 조합을 구하는 것처럼 생각해야 한다.

1번 2번 3번을 선택했으면, 3 2 1을 다시 선택하면 안된다는 얘기다.

DFS 함수에 매개변수를 잘 설정해주면 중복된 선택을 피할 수 있다.

 

 

 

 

3.문제풀이코드 C++

#include <bits/stdc++.h>
using namespace std;

//arr 배열에 true값으로 사다리 표시 
int arr[40][40],n,m,h,ans=INT_MAX, target_cnt;

// 사다리타기 체크 
bool check() {
	for (int i = 1; i <= n; i++) {
		int start = i;
		for (int j = 1; j <= h; j++) {
			if (start+1 <= n && arr[j][start] == true) {
				start++;
			}
			else if (start-1 >=1 && arr[j][start-1] == true) {
				start--;
			}
		}
		if (start != i) return false;
	}
	return true;
}

void DFS(int h_cnt, int n_cnt, int cnt) {

	// 사다리 선택하는 횟수를 통해 가지치기 하기
	if (cnt == target_cnt) {
		if (check()) {
			ans = cnt;
		}
		
		return;
	}

	//매개변수 설정을 잘 해주면 이전에 돌았던 거 다시 안돌아도 되서
	//가지치기를 할 수 있다. 
	for (int i = h_cnt; i <= h; i++, n_cnt = 1) {
		for (int j = n_cnt; j < n; j++) {
        	//연속된 사다리 선택 피해주기
			if (arr[i][j - 1] || arr[i][j] || arr[i][j + 1]) continue;
			else {
				arr[i][j] = 1;
				DFS(i, j, cnt + 1);
				arr[i][j] = 0;
			}
		}
	}

}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> n >> m >> h;

	for (int i = 0; i < m; i++) {
		int a, b;
		cin >> a >> b;
		arr[a][b] = true;
	}

	for (int i = 0; i <= 3; i++) {
		target_cnt = i;
		DFS(1, 1, 0);


		if (ans != INT_MAX) {
			cout << ans;
			return 0;
		}
	}

	cout << -1;

	return 0;
}

 

이 문제에 대해 구글링을 해보다가 다소 특이한 코드인데

효율적인 방법을 발견했다.

 

for (int i = h_cnt; i <= h; i++, n_cnt = 1) {
		for (int j = n_cnt; j < n; j++) {
        	//연속된 사다리 선택 피해주기
			if (arr[i][j - 1] || arr[i][j] || arr[i][j + 1]) continue;
			else {
				arr[i][j] = 1;
				DFS(i, j, cnt + 1);
				arr[i][j] = 0;
			}
		}
	}

 

백준 사다리조작 문제 해설

초록색 부분의 좌표가 위 코드에서 [h_cnt, n_cnt] 라고 할 때

회색 부분이 지금까지 탐색한 부분이고, 흰색 부분이 앞으로 탐색해야할 부분이다.

단순하게 2중 for문을 사용하면 탐색했던 부분을 불필요하게 다시 탐색해야 한다.

하지만

for (int i = h_cnt; i <= h; i++, n_cnt = 1) {
		for (int j = n_cnt; j < n; j++) {

i++, n_cnt = 1 이렇게 초기화 하는 부분을 넣어주면

i for 문이 처음 돌 때는 j의 값이 n_cnt의 값 부터 돌고

이후에는 n_cnt ==1이 되어서 계속 돌아준다.

이렇게 해주면 흰색 부분만 탐색해줄 수 있다.

 

불필요한 탐색을 줄여주기 위해 백트래킹, DFS의 매개변수와 같이 사용하면

상당히 편할 것 같다.

 

 

 

시간초과로 틀린 이유 - 틀린코드

#include <bits/stdc++.h>
using namespace std;

//arr 배열에 true값으로 사다리 표시 
int arr[40][40],n,m,h,ans=INT_MAX;

// 사다리타기 체크 
bool check() {
	for (int i = 1; i <= n; i++) {
		int start = i;
		for (int j = 1; j <= h; j++) {
			if (start+1 <= n && arr[j][start] == true) {
				start++;
			}
			else if (start-1 >=1 && arr[j][start-1] == true) {
				start--;
			}
		}
		if (start != i) return false;
	}
	return true;
}

void DFS(int c) {
	if (c > 3) {
		return;
	}

	if (check()) {
		ans = min(c,ans);

		return;
	}
	else {
		for (int i = 1; i <= h; i++) {
			for (int j = 1; j < n; j++) {
            // 이 부분에서 2중포문을 돌면서 DFS를 하면
            // DFS를 계속 돌면서 이전에 돌았던 걸 다시 돌게 되어 시간낭비가 발생한다.
				if (arr[i][j-1] || arr[i][j] || arr[i][j+1]) continue;
				else {
					arr[i][j] = 1;
					DFS(c + 1);
					arr[i][j] = 0;
				}
			}
		}
	}

}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> n >> m >> h;

	for (int i = 0; i < m; i++) {
		int a, b;
		cin >> a >> b;
		arr[a][b] = true;
	}

	DFS(0);

	if (ans <= 3) {
		cout << ans << '\n';
	}
	else {
		cout << -1 << '\n';
	}

	return 0;
}

 

 

 

백준 15684번 사다리 조작

시간이 많이 걸려서 여러번 다시 풀어 봤다.

DFS에서 중복된 선택을 피하기 위해 가지치기를 어떻게 할 지 잘 생각해야 시간을 줄일 수 있다.

 

반응형
반응형

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

 

14889번: 스타트와 링크

예제 2의 경우에 (1, 3, 6), (2, 4, 5)로 팀을 나누면 되고, 예제 3의 경우에는 (1, 2, 4, 5), (3, 6, 7, 8)로 팀을 나누면 된다.

www.acmicpc.net

1.문제설명

N명이 있을 때 N/2로 각각 팀을 나눠주고

문제에서 주어진 점수표로 각각 팀의 점수를 계산해

차이가 최소가 되도록 구해주어야한다.

 

 

2.접근방법[알고리즘]

2-1. 팀 나누기

DFS를 돌면서 조합으로 n명중 n/2명을 뽑아준다.

 

bool 타입 배열 ch에 뽑힌 번호는 true 안 뽑힌 번호는 false로 지정해줘서 두 팀으로 나눠줄 수 있다.

 

두 팀으로 나눠준 후 점수를 구해준다.

void DFS(int cur, int L) {
	if (L == n / 2) {
		ans = min(ans, cal());
	}
	else {
		for (int i = cur+1; i <= n; i++) {
			ch[i] = true;
			DFS(i, L + 1);
			ch[i] = false;
		}
	}
}

 

 

2-2. 점수구하기

int cal() {
	int point1=0, point2=0;
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= n; j++){
			if (ch[i] == true && ch[j] == true) 
				point1 += arr[i][j];
			if (ch[i] == false && ch[j] == false) 
				point2 += arr[i][j];
		}
	}
	return abs(point1 - point2);
}

만약 1과 2가 한팀이라고 가정하면

그 팀의 점수는 arr[1][2] + arr[2][1]이다.

 

만약 1, 2, 3이 한팀이라면

arr[1][2] + arr[1][3] + arr[2][1] + arr[2][3] + arr[3][1] + arr[3][2]

이렇게 된다.

 

참고로 문제에서 arr[i][i] = 0 으로 주기 때문에

arr[1][2] + arr[1][3] + arr[2][1] + arr[2][3] + arr[3][1] + arr[3][2]는

arr[1][1] + arr[1][2] + arr[1][3] + arr[2][1] + arr[2][2] + arr[2][3] + arr[3][1] + arr[3][2] + arr[3][3]과 동일하다.

그래서 위 코드처럼 하면 각 팀의 점수를 구해줄 수 있다.

 

점수를 구하는 과정도 DFS로 순열을 구현해서 할 수 있겠으나 

귀찮기도하고 이문제에서는 원소가 두개로 지정되어있으므로 for문으로 구현하는게 편하다.

 

 

3. 문제풀이코드 C++

#include <bits/stdc++.h>
using namespace std;

int arr[22][22], n, ans=INT_MAX;
bool ch[22];

//팀별 점수 계산해주는 함수 
int cal() {
	int point1=0, point2=0;
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= n; j++){
			if (ch[i] == true && ch[j] == true) 
				point1 += arr[i][j];
			if (ch[i] == false && ch[j] == false) 
				point2 += arr[i][j];
		}
	}
	return abs(point1 - point2);
}


//조합으로 팀 나눠주는 함수 
void DFS(int cur, int L) {
	if (L == n / 2) {
		ans = min(ans, cal());
	}
	else {
		for (int i = cur+1; i <= n; i++) {
			ch[i] = true;
			DFS(i, L + 1);
			ch[i] = false;
		}
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> arr[i][j];
		}
	}

	DFS(0, 0);

	cout << ans;
}

백준 14889번 메모리, 시간

 

반응형
반응형

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

 

2580번: 스도쿠

스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루

www.acmicpc.net

문제풀이코드는 맨 하단에 있습니다.

 

1.문제설명

백준 스도쿠 문제

스도쿠를 푸는 문제이다.

비어있는 칸을 채워주면 된다.

 

규칙은 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다

굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.

 

문제입력

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

빈칸은 0으로 입력된다.

출력은 스도쿠로 가능한 방법 중 하나를 출력하면 된다.

 

 

2.접근방법[알고리즘]

비워있는 칸을 규칙에 따라서 채워야한다.

빈칸, 즉 스도쿠에서 입력값이 0인 좌표를 배열에 저장한다.

 

백준 스도쿠 설명

 

일단 빈칸에 들어갈 숫자는 1~9 중 하나이고

가로줄,세로줄, 3x3 정사각형 안에 이미 등장했는지 안했는지 체크해준다.

 

2-1 체크 함수

//value 사용 할 수 있는지 체크하는 함수
//리턴 값 true면 사용가능
bool check(int x, int y, int value) {
	//가로 세로에서 value 이미 존재하는지 탐색
	for (int i = 0; i < 9; i++) {
		if(arr[i][y] == value) return false;
		if (arr[x][i] == value) return false;
	}
	//3x3 칸 내에 value 이미 존재하는지 탐색
	int part_x = x / 3;
	int part_y = y / 3;
	part_x *= 3;
	part_y *= 3;
	for (int i = part_x; i < part_x + 3; i++) {
		for (int j = part_y; j < part_y + 3; j++) {
			if (arr[i][j] == value) return false;
		}
	}
	return true;
}

 

check함수를 이용해 이미 등장한 숫자가 아니라면 숫자를 넣어보고

다음 빈칸을 똑같은 과정을 수행해본다.

그리고 모든 빈칸에 숫자를 넣는다면 함수를 종료해준다.

 

그리고 정답이 나올 때까지 모든 경우에 탐색을 해봐야하므로

빈칸에 숫자를 넣고 DFS를 시행해보고 다시 그 빈칸을 초기화해주어야한다.

초기화해주지 않으면 모든 숫자가 0 인 경우에 대해서 스도쿠를 꽉 채우지 못한다.

DFS함수

void DFS(int cur) {
	if (isend == true) return;
	if (cur == L) {
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				cout << arr[i][j] << ' ';
			}
			cout << '\n';
		}
		isend = true;
	}
	else{
		int nx = v[cur].first;
		int ny = v[cur].second;
		for (int i = 1; i <= 9; i++) {
			if (check(nx, ny, i)) {
				arr[nx][ny] = i;
				DFS(cur + 1);
				//위에 DFS가 정답이 아닐 수도 있으니 초기화하고 돌아줘야한다.
				arr[nx][ny] = 0;
			}
		}
	}
}

 

스도쿠 반례

0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0

 

 

 

 

 

 

3.문제풀이코드 C++

#include <bits/stdc++.h>
using namespace std;

int arr[9][9], L;
vector<pair<int, int> > v;

bool isend = false;
//value 사용 할 수 있는지 체크하는 함수
//리턴 값 true면 사용가능
bool check(int x, int y, int value) {
	for (int i = 0; i < 9; i++) {
		if(arr[i][y] == value) return false;
		if (arr[x][i] == value) return false;
	}
	//3x3 칸 내에 value 이미 존재하는지 탐색
	int part_x = x / 3;
	int part_y = y / 3;
	part_x *= 3;
	part_y *= 3;
	for (int i = part_x; i < part_x + 3; i++) {
		for (int j = part_y; j < part_y + 3; j++) {
			if (arr[i][j] == value) return false;
		}
	}
	return true;
}

void DFS(int cur) {
	if (isend == true) return;
	if (cur == L) {
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				cout << arr[i][j] << ' ';
			}
			cout << '\n';
		}
		isend = true;
	}
	else{
		int nx = v[cur].first;
		int ny = v[cur].second;
		for (int i = 1; i <= 9; i++) {
			if (check(nx, ny, i)) {
				arr[nx][ny] = i;
				DFS(cur + 1);
				//위에 DFS가 정답이 아닐 수도 있으니 초기화하고 돌아줘야한다.
				arr[nx][ny] = 0;
			}
		}
	}
}


int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	for (int i = 0; i < 9; i++) {
		for (int j = 0; j < 9; j++) {
			cin >> arr[i][j];
			if (arr[i][j] == 0) {
				v.push_back({ i,j });
			}
		}
	}
	L = v.size();
	DFS(0);
}

2580번 메모리 및 시간

4. 문제풀이후기

빈칸에 넣을 수 있는 숫자를 계속 넣어보면서

백트래킹을 수행하면 풀 수 있는 문제였습니다.

반응형

+ Recent posts