반응형

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

 

2529번: 부등호

여러분은 제시된 부등호 관계를 만족하는 k+1 자리의 최대, 최소 정수를 첫째 줄과 둘째 줄에 각각 출력해야 한다. 단 아래 예(1)과 같이 첫 자리가 0인 경우도 정수에 포함되어야 한다. 모든 입력

www.acmicpc.net

백트래킹을 이용한 풀이는

 

코드가 직관적이므로 별도로 설명하진 않겠습니다.

 

백트래킹 코드

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

int n;
char arr[20];
bool ch[10];
int path[20];

long long minn = LLONG_MAX, maxx = LLONG_MIN;

void DFS(int L) {
    if (L == n + 1) {
        for (int i = 1; i <= n; i++) {
            if (arr[i] == '<') {
                if (path[i - 1] > path[i]) {
                    return;
                }
            }
            else {
                if (path[i - 1] < path[i]) {
                    return;
                }

            }
        }


        long long x = 0;

		for (int i = 0; i < L; i++) {
			x = x * 10 + path[i];
		}

        minn = min(minn, x);
        maxx = max(maxx, x);

    }
    else {
        for (int i = 0; i <= 9; i++) {
            if (ch[i] == 0) {
				ch[i] = 1;
				path[L] = i;
				DFS(L + 1);
				ch[i] = 0;
			}
        }
    }
}


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

    cin >> n;

    long long comp = pow(10, n);

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


    DFS(0);

    cout << maxx << '\n';

    if (minn < comp) {
        cout << 0;
    }

    cout << minn << '\n';



    return 0;
}

 

 

 


백준 2529 부등호 위상정렬로 푸는 법

 

위상정렬을 공부하다 다른 블로그의 설명을 많이 봤는데 이해가 잘 되지 않았어서

한참 헤맸습니다.

 

노드 번호와 해당 노드의 숫자를 별개로 생각해야합니다.

 

A노드와 B 노드가 있다고 가정합니다.

부등호를 기준으로

A > B 라면 A -> B 를 향하는 간선을 주고

위상정렬을 수행합니다.

 

 

예제에서 먼저 최댓값을 생각해보면

2
< >

A < B > C 이면

 

B -> A

B -> C 가 간선이 되고

 

위상정렬을 수행하면 순서가

B A C 이거나

B C A 가 됩니다.

 

이 뜻은 B가 가장 커야하고 A와 C는 따로 정해주어야 한다는 뜻입니다.

그래서 B는 9가 되고 A C 가 8 7이 되어야 하는데

최댓값을 구할 때 먼저 나온게 커야하므로 A에 8을 주고 C에 7을주면

B : 9 ,

A : 8,

C : 7,

 

이 되고 원래 숫자는 ABC 순서이므로

897이 최대가 됩니다.

 


 

최솟값을 구할 땐

가장 큰 수인 B에 숫자를 넣을 때 9부터 넣는게 아닌 K(2)부터 넣어야 최솟값이 구해집니다.

그리고 A 와 C를 정할 때 앞에 있는 숫자가 작은 숫자여야 최솟값이 되므로 A에 0 C에 1을 넣으면 됩니다.

그러면

B : 2

A : 0

C : 1

이므로 정답은 ABC 021이 됩니다.

 

 


간선 정보는 동일하게 이용하고 degree(차수)를 줄여주면서 큐를 돌 때

 

노드번호를 각각 최소힙, 최대힙으로 주면서 뽑아주면서

 

노드에 숫자를 하나씩 넣으면 됩니다.

 

 

 

 

위상정렬 코드

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

int k;
char arr[20];

int node[11]; // 최댓값 구하는데 사용
int degree[11]; 
int node2[11]; // 최솟값 구하는데 사용
int degree2[11]; 
bool ch[11][11];


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

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

        if (arr[i] == '<') {
            ch[i + 1][i] = 1;
            degree[i]++;
            degree2[i]++;
        }
        else{
            ch[i][i + 1] = 1;
            degree[i + 1]++;
            degree2[i + 1]++;
        }
    }

    priority_queue<int> Q;

    //최댓값 구하기 

    for (int i = 1; i <= k + 1; i++) {
        if (degree[i] == 0) {
            Q.push(-i);
        }
    }

    int cnt = 9;
    while (!Q.empty()) {
        int x = -Q.top();
        Q.pop();
        node[x] = cnt--;
        for (int i = 1; i <= k + 1; i++) {
            if (ch[x][i] == 1) {
                if (--degree[i] == 0) {
                    Q.push(-i);
                }
            }
        }
    }

    for (int i = 1; i <= k + 1; i++) {
        cout << node[i];
    }
    cout << '\n';





    //최솟값 구하기

    for (int i = 1; i <= k + 1; i++) {
        if (degree2[i] == 0) {
            Q.push(i);
        }
    }

    cnt = k;
    while (!Q.empty()) {
        int x = Q.top();
        Q.pop();
        node2[x] = cnt--;
        for (int i = 1; i <= k + 1; i++) {
            if (ch[x][i] == 1) {
                if (--degree2[i] == 0) {
                    Q.push(i);
                }
            }
        }
    }

    for (int i = 1; i <= k + 1; i++) {
        cout << node2[i];
    }
    cout << '\n';


   
    return 0;
}

위상정렬을 이용했을 때 시간은 0ms

백트래킹을 이용했을 때 120ms

백트래킹에서 가지치기를 하진 않았지만, 위상정렬을 이용할 때

더 빠른 시간 내에 문제를 해결 할 수 있습니다.

 

 

반응형
반응형

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

 

14888번: 연산자 끼워넣기

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 

www.acmicpc.net

 

 

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

int n, arr[12], op[4], max_ans = INT_MIN, min_ans = INT_MAX;

void DFS(int x, int res) {

	if (x == n - 1) {
		max_ans = max(max_ans,res);
		min_ans = min(min_ans,res);
	}

	for (int i = 0; i < 4; i++) {
		if (op[i] > 0) {
			op[i]--;

			if (i == 0) {
				DFS(x + 1, res + arr[x + 1]);
			}
			else if (i == 1) {
				DFS(x + 1, res - arr[x + 1]);

			}
			else if (i == 2) {
				DFS(x + 1, res * arr[x + 1]);

			}
			else if (i == 3) {
				DFS(x + 1, res / arr[x + 1]);
			}
			op[i]++;
		}
	}

}


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


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

	for (int i = 0; i < 4; i++) {
		cin >> op[i];
	}

	DFS(0, arr[0]);

	cout << max_ans << '\n';
	cout << min_ans << '\n';


	return 0;
	
}

백준 14888번 연산자끼워넣기

반응형
반응형

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

 

17142번: 연구소 3

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 바이러스는 활성 상태와 비활성 상태가 있다. 가장 처음에 모든 바이러스는 비활성 상태이고

www.acmicpc.net

1.문제설명

 

7 3
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 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 0 0 2

 

첫째 줄에 연구소의 크기 N(4 ≤ N ≤ 50), 놓을 수 있는 바이러스의 개수 M(1 ≤ M ≤ 10)이 주어진다.

둘째 줄부터 N개의 줄에 연구소의 상태가 주어진다.

0은 빈 칸, 1은 벽, 2는 바이러스를 놓을 수 있는 위치이다.

2의 개수는 M보다 크거나 같고, 10보다 작거나 같은 자연수이다.

2는 비활성화된 바이러스를 의미하고 2 중에서 m개를 선택해 바이러스를 활성화시킬 때

벽을 제외한 모든 영역이 바이러스가 되는 최소 시간을 구해야한다.

 

 

 

2.문제접근[알고리즘]

https://dingcoding.tistory.com/90

 

백준 14502번: 연구소 - DFS, BFS, 백트래킹 C++ 풀이

https://www.acmicpc.net/problem/14502 14502번: 연구소 인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서

dingcoding.tistory.com

https://dingcoding.tistory.com/91

 

백준 17141번 : 연구소 2 - BFS DFS 백트래킹 응용 문제

https://www.acmicpc.net/problem/17141 17141번: 연구소 2 인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 승원이는 연구소의 특정 위치에 바이러스 M개를

dingcoding.tistory.com

 

연구소 3번 문제는 연구소 1번 2번 문제와 비슷하나 가장 중요한 차이점은

초기에 모든 바이러스는 비활성화 상태라는 점이다.

문제에서 m개의 비활성화 바이러스를 선택해

벽을 제외한 모든 영역이 바이러스가 되는 최소 시간을 구해야한다.

모든 영역은 비활성화 바이러스거나 활성화 바이러스거나 상관 없이 바이러스이기만 하면 된다.

 

 

문제풀이과정

1. DFS를 통해 m개의 비활성화 바이러스를 선택해 활성화 바이러스로 만든다.

2. 선택된 활성화바이러스로 BFS를 하고, 벽을 제외한 모든영역에 바이러스를 퍼트리는 시간을 구한다.

3. DFS를 돌며 m개를 다르게 선택하며 최소 시간을 구해준다.

4. 바이러스를 어떤 방법으로도 전체 영역에 퍼트릴 수 없다면 -1, 아니면 최소시간을 출력한다.

 

 

 

3.문제풀이코드 C++

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

int n, m, arr[51][51], ans=INT_MAX, total_zero;
int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };
bool ch[51][51];

bool virus_select[11];
vector<pair<int, int> > virus;
queue<pair<int,int> > Q;

void BFS() {
	int count_zero = 0;

	//활성화된 바이러스 Q에 넣어줌 
	for (int i = 0; i < virus.size(); i++) {
		if (virus_select[i] == 1) {
			Q.push({ virus[i].first, virus[i].second});
			ch[virus[i].first][virus[i].second] = 1;
		}
	}

	int time = 0;
	while (!Q.empty()) {
		int cur_size = Q.size();

		/*0인 영역에 바이러스를 다 퍼트렸는지 체크*/
		if (total_zero == count_zero) {
			ans = min(time, ans);
			while (!Q.empty()) Q.pop();
			break;
		}

		/*같은 시간에 새로 추가된 모든 바이러스를 BFS로 탐색*/
		for (int j = 0; j < cur_size; j++) {
			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 >= n) continue;
				if (arr[nx][ny] == -1) continue; //벽이면 continue
				if (ch[nx][ny] == 0) { //비활성화된 바이러스, 0인 영역 모두 방문
					ch[nx][ny] = 1;
					Q.push({ nx, ny });
					if (arr[nx][ny] == 0) count_zero++; //0인 영역을 방문한 거라면 0인 영역에 바이러스 퍼진 갯수 세줌
				}
			}
		}
		time++;
	}

	//DFS에서 또 ch를 사용하므로 BFS함수 이전 원래 상태로 초기화
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			ch[i][j] = 0;
		}
	}

}

void DFS(int cur, int L) {
	if (L == m) {
		BFS();
	}
	else {
		for (int i = cur; i < virus.size(); i++) {
			virus_select[i] = 1;
			DFS(i + 1, L + 1);
			virus_select[i] = 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 < n; j++) {
			cin >> arr[i][j];
			
			if (arr[i][j] == 2) virus.push_back({ i,j });
			else if(arr[i][j] == 0) total_zero++;
		}
	}

	DFS(0, 0);

	if (ans == INT_MAX) {
		cout << -1;
	}
	else {
		cout << ans;
	}

}

백준 17142 연구소3 해결 메모리 및 시간

 

4.틀린 이유

1. 시간초과 이유 : 바이러스를 벽을 제외한 모든 영역에 퍼트렸는지 확인

/*0인 영역에 바이러스를 다 퍼트렸는지 체크*/
		if (total_zero == count_zero) {
			ans = min(time, ans);
			while (!Q.empty()) Q.pop();
			break;
		}

 

0의 개수를 세는 변수를 따로 설정해 바이러스가 된 0의 개수를 세서 비교했다.

이전에는 n*n 배열을 모두 탐색해 0이 있는지 없는지 확인해서 시간초과가 나서 틀렸다.

 

 

2. 비활성화된 바이러스도 바이러스다.

처음에 문제를 잘 이해하지 못해 모든 바이러스를 활성화 시켜야 끝나는 줄 알고 그렇게 했는데

비활성화 여부와 관계없이 모든 영역이 바이러스로 바뀌어있으면 BFS를 종료하면 된다.

 

 

반응형
반응형

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

 

17141번: 연구소 2

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 승원이는 연구소의 특정 위치에 바이러스 M개를 놓을 것이고, 승원이의 신호와 동시에 바이

www.acmicpc.net

1.문제설명

7 3
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 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 0 0 2

N*N 정사각형 배열이 주어진다. 1은 바이러스가 지나갈 수 없는 벽을 의미한다.

m은 바이러스를 놓을 수 있는 개수이고, 2는 바이러스를 놓을 수 있는 공간이다.

2가 바이러스가 놓여진 게 아니라 놓을 수 있는 공간인 점이 중요하다.

m개의 바이러스를 2가 쓰여진 공간에 두었을 때 

벽을 제외한 모든 공간에 바이러스를 퍼트린다면 

바이러스를 퍼트리는데 걸리는 최소 시간을 구해야한다.

그 최소 시간을 출력해야한다.

 

만약 바이러스 m개를 어떻게 두어도 벽을 제외한 모든 공간에 바이러스를 퍼트리지 못한다면

-1을 출력한다.

 

 

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

1. 백트래킹으로 바이러스 놓는 칸 m개 선택
2. m개를 선택했을 때 바이러스 다 퍼트릴 수 있는 지 BFS해보고 퍼트릴 수 있다면 시간을 구한다.
3. m개를 선택하는 모든 방법에 대하여 시간을 구해보고 최소 시간을 뽑는다.
4. 어떤 방법으로도 바이러스를 모든 공간에 퍼트릴 수 없다면 -1을 출력한다.

 

 

 

 

 

 

3.문제풀이코드 C++

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

int n, m, arr[51][51], ans=INT_MAX;
int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };
vector<pair<int, int > > virus;
queue<pair<int, int> > Q;
//1. 바이러스 놓는 칸 m개 선택
//2. 바이러스 다 퍼트렸는지 확인
//3. 다 퍼트렸다면 최소시간 확인
//4. 다 퍼트린게 없다면 -1 출력

void print() {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (arr[i][j] == -1) cout << '-' << ' ';
			else cout << arr[i][j] <<' ';
		}
		cout << '\n';
	}
	cout << "------------\n";

}

int check() {
	int minute = 0;
	//print();

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (arr[i][j] == -1) continue;

			if (arr[i][j] == 0) {
				return INT_MAX;
			}
			else {
				minute = max(arr[i][j], minute);
			}
		}
	}
	return minute;
}

void BFS() {

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (arr[i][j] == 1) {
				Q.push({ i,j });
			}
		}
	}

	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 >= n) continue;
			if (arr[nx][ny] == 0) {
				arr[nx][ny] = arr[x][y] + 1;
				Q.push({ nx,ny });
			}
		}
	}
}

void initialize() {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (arr[i][j] > 1) {
				arr[i][j] = 0;
			}
		}
	}
}

void DFS(int cur, int L) {
	if (L == m) {	
		BFS();
		ans = min(ans, check());
		initialize();
	}
	else {
		for (int i = cur; i < virus.size(); i++) {
			//바이러스는 arr에서 1로 체크해준다.
			int x = virus[i].first;
			int y = virus[i].second;
			if (arr[x][y] == 0) {
				arr[x][y] = 1;
				DFS(i + 1, L + 1);
				arr[x][y] = 0;
			}
		}
	}
}

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

	queue<pair<int, int > > Q;

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> arr[i][j];
			if (arr[i][j] == 1) {
				arr[i][j] = -1;
			}
			if (arr[i][j] == 2) {
				arr[i][j] = 0;
				virus.push_back({ i,j });
			}
		}
	}


	DFS(0,0);

	if (ans == INT_MAX) {
		cout << -1;
	}
	else {
		cout << ans-1;
	}

}

백준 17141번 연구소 문제 메모리 및 시간

 

 

 

반응형
반응형

 

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/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. 문제풀이후기

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

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

반응형
반응형

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

 

1987번: 알파벳

세로 R칸, 가로 C칸으로 된 표 모양의 보드가 있다. 보드의 각 칸에는 대문자 알파벳이 하나씩 적혀 있고, 좌측 상단 칸 (1행 1열) 에는 말이 놓여 있다. 말은 상하좌우로 인접한 네 칸 중의 한 칸으

www.acmicpc.net

1.문제설명

2 4
CAAB
ADCB

다음과 같이 알파벳으로 이루어진 보드가 주어질 때

각 알파벳은 한 번씩만 방문할 수 있다.

좌측 상단(C)에서 시작해서 상하좌우로 이동하여

이동할 수 있는 최대 칸 수를 구해야한다.

 

 

백준 1987번 알파벳
백준 1987번 알파벳

D로 이동하면 D에서 상하좌우를 보면

위는 이미 방문해서 갈 수 없고 좌우는 이미 지난 알파벳이어서 방문할 수 없어서

D에서 깊이탐색이 끝나게 되고,

이동 거리는 3이다.

 

 

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

이 문제는 말이 최대한 몇칸을 움직일 수 있는지 구해야하는 문제다.

보통 DFS나 BFS로 최단거리를 많이 구하는데 이 문제는 다르게

이동할 수 있는 최대 거리를 구해야한다.

 

그리고 각 알파벳 마다 한번 씩만 방문할 수 있다.

방문한 알파벳을 저장해주면서 DFS 탐색을 돌아

최대 이동 거리를 갱신해주어야 한다.

 

 

2-1. 방문한 노드 좌표는 따로 저장을 안해주어도 되나?

방문한 알파벳을 저장해주면 알파벳 정보에

방문한 노드 정보까지 자연스럽게 포함된다

예를 들어 전에 방문한 노드가 C라면 C를 저장해주면

노드 번호를 따로 저장해주지 않아도 다음 노드가 C이면 탐색을 하지 않는다.

그러므로 방문한 알파벳만 저장하고 체크하면 된다.

 

 

2-2. DFS 코드

DFS(x좌표, y좌표, 이동거리) 이렇게 매개변수를 두고

dist가 최대일 때마다 갱신해주면서 깊이탐색 했다.

 

bool 배열 ch_alpha에 해당 알파벳이 이미 방문된 알파벳인지 체크해준다.

백트래킹을 돌면서 최대 이동거리를 갱신한다.

int r, c, ans;
char arr[21][21];
bool ch_alpha[26];

int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

void DFS(int x, int y, int dist) {
	
	ans = max(ans, dist);

	for (int i = 0; i < 4; i++) {
		int nx = x + dx[i];
		int ny = y + dy[i];
		if (nx < 0 || nx >= r || ny < 0 || ny >= c) continue;
		
		int next_alpha = arr[nx][ny] - 'A';
		if (ch_alpha[next_alpha] == 0) {
			ch_alpha[next_alpha] = 1;
			DFS(nx, ny, dist + 1);
			ch_alpha[next_alpha] = 0;
		}
	}
}

 

 

 

 

3.문제풀이코드 C++

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

int r, c, ans;
char arr[21][21];
bool ch_alpha[26];

int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

void DFS(int x, int y, int dist) {
	
	ans = max(ans, dist);

	for (int i = 0; i < 4; i++) {
		int nx = x + dx[i];
		int ny = y + dy[i];
		if (nx < 0 || nx >= r || ny < 0 || ny >= c) continue;
		
		int next_alpha = arr[nx][ny] - 'A';
		if (ch_alpha[next_alpha] == 0) {
			ch_alpha[next_alpha] = 1;
			DFS(nx, ny, dist + 1);
			ch_alpha[next_alpha] = 0;
		}
	}
}

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

	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			cin >> arr[i][j];
		}
	}

	ch_alpha[arr[0][0] - 'A'] = 1;
	DFS(0, 0, 1);

	cout << ans << '\n';

}

백준 1987번 시간 및 메모리

 

반응형
반응형

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

 

 

1759번: 암호 만들기

첫째 줄에 두 정수 L, C가 주어진다. (3 ≤ L ≤ C ≤ 15) 다음 줄에는 C개의 문자들이 공백으로 구분되어 주어진다. 주어지는 문자들은 알파벳 소문자이며, 중복되는 것은 없다.

www.acmicpc.net

1. 문제설명

순열이나 조합을 만드는 문제와 비슷하다. 차이점은 숫자 대신 문자를 출력한다는 점이다.

조건에서 구해야 하는 암호는 항상 사전순으로 이루어져있어야하고

최소 한 개의 모음(a, e, i, o, u)과 최소 두 개의 자음으로 구성되어 있어야한다.

 

 

입력

4 6
a t c i s w

출력

acis
acit
aciw
acst
acsw
actw
aist
aisw
aitw
astw
cist
cisw
citw
istw

 

 

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

먼저 증가하는 순열(조합)을 만들고 그 순열에 알파벳 순으로 덮어준다고 생각하면 쉽다.

다만 문제에서 사전순이라는 조건과 모음과 자음의 갯수 조건이 있으므로

우선 알파벳 순열을 만들어주고 그 알파벳 순열이 조건에 부합한다면 출력해주면 된다.

 

 

 

우선 증가하는 순열을 만들어보자. 

6개의 숫자에서 4개의 숫자를 뽑는 조합을 만든다고 생각해도 동일하다.

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

int L, C, path[20], ch[20] , t=1;
char a[20], ans[20];

void DFS(int cur) {
	if (t == L+1) {
		for (int i = 1; i <= L; i++) {
			cout << path[i] << ' ';
		}
		cout << '\n';

	}
	else {
		for (int i = cur; i <= C; i++) {
			if (ch[i] == 0) {
				ch[i] = 1;
				path[t++] = i;
				DFS(i);
				ch[i] = 0;
				t--;
			}
		}
	}
}

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


	DFS(1);
}

 

조합 출력

 

이제 각 숫자에 해당 알파벳을 적용하면 된다

알파벳은 문제 조건에 따라 사전순으로 정렬되어야한다.

a t c i s w
번호 1 2 3 4 5 6
알파벳 a c i s t w

 

1759 암호만들기

이제 알파벳 까지 적용해서 잘 만들었다.

하지만 cstw는 문제 조건에서 모음이 한개이상 있어야하므로 출력되면 안된다.

↓ 지금 단계 까지의 코드

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

int L, C, path[20], ch[20] , t=1;
char a[20], ans[20];

void DFS(int cur) {
	if (t == L+1) {
		int cnt1 = 0;
		//for (int i = 1; i <= L; i++) {
		//	ans[i] = a[path[i]];
		//	if (ans[i] == 'a' || ans[i] == 'e' ||
		//		ans[i] == 'i' || ans[i] == 'o' || ans[i] == 'u') {
		//		cnt1++;
		//	}
		//}

		//if (cnt1 >= 1 && L - cnt1 >= 2) {
		//	for (int i = 1; i <= L; i++) {
		//		cout << ans[i];
		//	}
		//	cout << '\n';

		//}

		for (int i = 1; i <= L; i++) {
			cout << a[path[i]] << ' ';
		}
		cout << '\n';

	}
	else {
		for (int i = cur; i <= C; i++) {
			if (ch[i] == 0) {
				ch[i] = 1;
				path[t++] = i;
				DFS(i);
				ch[i] = 0;
				t--;
			}
		}
	}
}

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

	for (int i = 1; i <=C; i++) {
		cin >> a[i];
	}
	sort(a+1 , a + 1 + C);

	DFS(1);
}

 

마지막으로 출력하기전에 모음과 자음 갯수를 카운트해

조건에 부합하면 출력하면된다.

cnt1 변수로 모음갯수만 세서 전체 알파벳 갯수인 L에서 cnt1을 빼주면 자음갯수를 구할 수 있다.

 

3.문제풀이코드 C++

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

int L, C, path[20], ch[20] , t=1;
char a[20], ans[20];

void DFS(int cur) {
	if (t == L+1) {
		int cnt1 = 0;
		for (int i = 1; i <= L; i++) {
			ans[i] = a[path[i]];
			if (ans[i] == 'a' || ans[i] == 'e' ||
				ans[i] == 'i' || ans[i] == 'o' || ans[i] == 'u') {
				cnt1++;
			}
		}

		if (cnt1 >= 1 && L - cnt1 >= 2) {
			for (int i = 1; i <= L; i++) {
				cout << ans[i];
			}
			cout << '\n';

		}
	}
	else {
		for (int i = cur; i <= C; i++) {
			if (ch[i] == 0) {
				ch[i] = 1;
				path[t++] = i;
				DFS(i);
				ch[i] = 0;
				t--;
			}
		}
	}
}

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

	for (int i = 1; i <=C; i++) {
		cin >> a[i];
	}
	sort(a+1 , a + 1 + C);

	DFS(1);
}

DFS, 백트래킹을 이용하여 조합을 만들어주고

 

문제 조건을 적용하면 해결되는 문제이다.

반응형

+ Recent posts