1. 문제

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

 

1799번: 비숍

첫째 줄에 체스판의 크기가 주어진다. 체스판의 크기는 10이하의 자연수이다. 둘째 줄부터 아래의 예와 같이 체스판의 각 칸에 비숍을 놓을 수 있는지 없는지에 대한 정보가 체스판 한 줄 단위로

www.acmicpc.net

2. 문제의 요구

체스에서 대각선으로 공격할 수 있는 "비숍"을 서로 공격하지 못하도록

체스판에 배치할 수 있는 최대 개수를 구하여라.

 

3. 풀이

#적용할 알고리즘 : 백트래킹

일단은 단순하게 모든 칸에 대해 각 비숍을 배치할 때 마다

그 대각선 방향에 전에 배치했던 비숍의 유무를 확인하고 없다면

그 자리에 배치하는 방식의 알고리즘을 생각할 수 있다.

하지만 이런 방식은 시간초과가 났다.

여기서 새로운 발상이 필요한데 그것은 바로

#새로운 발상 : 비숍을 놓을 때 체스판의 검은 부분과 흰 부분은 서로 관련이 없다.

비숍을 흰 부분에 놓으면 그 비숍이 공격할 수 있는 곳은 흰 부분 뿐이다.

따라서 체스판의 흰 부분과 검은 부분을 분리하여 따로 백트래킹한다.

이렇게 하면 (10 * 10) = 100 에서 ((5 * 5) * 2) = 50 이 되기 때문에 확실한 이득을 볼 수 있다.

 

4. 코드

#include<iostream>
#include<string>
#include<stack>
#include<tuple>
#include<queue>
#include<algorithm>
#include<bitset>

using namespace std;

#define endl (cout << '\n')

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

int n;

int board[12][12];
bool isUsed[12][12];

vector<pair<int, int>> white;
vector<pair<int, int>> black;

int mx = 0;

//대각선 라인에 다른 비숍이 있는지 확인
//있으면 => 되돌아감
//없으면 => 계속
bool Check(int x, int y)
{
    for (int dir = 0; dir < 4; dir++)
    {
        for (int i = 0; true; i++)
        {
            if (x + i * dx[dir] < 0 || x + i * dx[dir] >= n || y + i * dy[dir] < 0 || y + i * dy[dir] >= n) break;
            //cout << x << ' ' << y << '\n';
            //cout << i << ' ' << dx[dir] << ' ' << dy[dir] << ' ' << x + i * dx[dir] << ' ' << y + i * dy[dir] << '\n';
            if (isUsed[x + i * dx[dir]][y + i * dy[dir]])
            {
                return false;
            }
        }
    }
    return true;
}

//백트래킹
void solve(int cur, vector<pair<int, int>>& color, int start)
{
    mx = max(mx, cur);

    if (cur == color.size()) return;

    for (int i = start; i < color.size(); i++)
    {
        int cx, cy;
        tie(cx, cy) = color[i];

        if (isUsed[cx][cy]) continue;

        if (Check(cx, cy))
        {
            isUsed[cx][cy] = 1;
            solve(cur + 1, color, i + 1);
            isUsed[cx][cy] = 0;
        }
    }
}

bool isWhite[12][12];

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

    cin >> n;

    for (int i = 0; i < n; i += 2) isWhite[0][i] = 1;
    for (int i = 1; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            isWhite[i][j] = (isWhite[i - 1][j] ? false : true);
        }
    }

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cin >> board[i][j];
            if (board[i][j] == 1)
            {
            ///화이트 칸과 블랙 칸을 나눠서 벡터에 저장
                if (isWhite[i][j]) white.push_back({i, j});
                else black.push_back({ i, j });
            }
        }
    }

    int ans = 0;

	//화이트 칸과 블랙칸이
	//서로 연관이 없기 때문에 가능하다
	//화이트 칸에서 비숍 배치
    solve(0, white, 0);
    ans += mx;
    mx = 0;
    //블랙 칸에서 비숍 배치
    solve(0, black, 0);
    ans += mx;

    cout << ans;

}

5. 느낀 점

시간을 줄일 방법이 생각이 안나서 결국 답을 찾아봤다.

이런 발상을 좀 더 쉽게 할 수 있도록 계속 생각해야한다.

1. 문제

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

 

2580번: 스도쿠

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

www.acmicpc.net

2. 문제의 요구

빈칸을 채워 스도쿠를 완성하라!

 

3. 적용할 알고리즘

백트래킹이 가능하다.

처음에는 빈칸이 10개인 경우 9의 10승으로 시간초과가 날 거라 예상했지만

빈칸을 채울 때마다 조건 확인을 해주면 그 다음 재귀로 넘어가는 경우의 수는 많이 줄어든다.

 

4. 설명

빈칸에 1~9까지 수를 대입해보고

스도쿠 조건에 만족하지 않는다면 그 경우는 패스하고,

만족한다면 다음 빈칸으로 넘어간다.

만약 만족하는 수가 없다면 이전 선택이 잘못된 것이므로

이전의 빈칸으로 돌아간다.

빈칸이 모두 채워졌다면 종료한다.

 

5. 코드

#include<iostream>
#include<vector>
#include<string>
#include<stack>
#include<queue>
#include<tuple>

using namespace std;

const int INF = 0x7f7f7f7f;

typedef long long ll;

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

int board[10][10];

vector<pair<int,int>> zero;

bool Check(int x, int y, int num)
{
	for (int i = 0; i < 9; i++)
	{
		if (board[i][y] == num) return false;
		if (board[x][i] == num) return false;
	}

//주변 9개 칸 중 같은 것이 있는지 확인
//*주의* C언어에서 x / 3 * 3 != x 이다. 
// '/'연산은 나머지를 날려버리기 때문이다.
	for (int i = x / 3 * 3; i < x / 3 * 3 + 3; i++)
	{
		for (int j = y / 3 * 3; j < y / 3 * 3 + 3; j++)
		{
			if (board[i][j] == num) return false;
		}
	}

	return true;
}

int ans[10][10];

void solve(int cur)
{
//빈 칸을 다 채웠을 시 종료
	if (cur == zero.size())
	{
		for (int i = 0; i < 9; i++)
			for (int j = 0; j < 9; j++) ans[i][j] = board[i][j];
		return;
	}

	int cx, cy;
	tie(cx, cy) = zero[cur];

//적절한 수인지 확인 후 다음 빈칸으로 넘어간다
	for (int i = 1; i <= 9; i++)
	{
		if (Check(cx, cy, i))
		{
			board[cx][cy] = i;
			solve(cur + 1);
			board[cx][cy] = 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 >> board[i][j];
			if (board[i][j] == 0) zero.push_back({ i, j });
		}
	}

	solve(0);

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

6. 느낀 점

그냥 대충 순열, 조합 등으로 계산하고 백트래킹 불가로 생각하지 말고

어떠한 조치를 취하면 백트래킹의 시간복잡도가 대폭 줄어들어

완전탐색이 가능하다는 걸 기억하자

'알고리즘&자료구조 실전문제풀이 > 백트래킹' 카테고리의 다른 글

Boj 1799 비숍  (0) 2023.04.18
boj 16987 계란으로 계란치기  (0) 2023.04.14

 

1. 문제 :https://www.acmicpc.net/problem/16987

 

16987번: 계란으로 계란치기

원래 프로그래머의 기본 소양은 팔굽혀펴기를 단 한 개도 할 수 없는 것이라고 하지만 인범이는 3대 500을 넘기는 몇 안되는 프로그래머 중 한 명이다. 인범이는 BOJ에서 틀린 제출을 할 때마다 턱

www.acmicpc.net

2. 문제에서 원하는 것

계란을 왼쪽에서 오른쪽으로 한 칸씩 움직이며 맨 오른쪽 끝에 있는 계란 까지 손에 들고

다른 임의의 계란을 들어서 부딪혔을 때 계란을 최대 몇 개 깰 수 있는가?

 

3. 적용할 알고리즘

총 계란의 개수가 8개이므로 계란을 들고 어떤 계란과 부딪힐지 모든 경우의 수를

조사하면 총 연산 횟수는 대략 최대 7의 8승(5,764,801)이 되어서

백트래킹을 이용한 완전 탐색이 가능하다.

 

4. 수학적 사고, 새로운 발상

필요 없음, 그냥 단순한 백트래킹

 

5. 코드

#include<iostream>
#include<string>
#include<stack>
#include<tuple>
#include<queue>
#include<algorithm>
#include<bitset>

using namespace std;

int n;

pair<int, int> egg[10];

int ans = 0;

void solve(int cur)
{
    if (cur == n)
    {
        int cnt = 0;
        for (int i = 0; i < n; i++)
        {
            //cout << egg[i].first << ' ';
            if (egg[i].first <= 0) cnt++;
        }
        ans = max(ans, cnt);
        return;
    }

    if (egg[cur].first <= 0)
    {
        solve(cur + 1);
        return;
    }

    for (int i = 0; i < n; i++)
    {
        if (cur == i) continue;
        if (egg[i].first <= 0) continue;

        egg[cur].first -= egg[i].second;
        egg[i].first -= egg[cur].second;
        solve(cur + 1);
        egg[cur].first += egg[i].second;
        egg[i].first += egg[cur].second;
    }

    if (cur == n - 1) solve(cur + 1);
}

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

    cin >> n;
    
    for (int i = 0; i < n; i++) cin >> egg[i].first >> egg[i].second;

    solve(0);

    cout << ans;
}

6. 느낀 점

쉬운 문제인데 문제 잘못읽어서 1시간 동안 뻘짓했다ㅋㅋ

앞으로는 문제를 잘 읽고 풀이가 확실히 떠오를 때 구현을 시작하자...

'알고리즘&자료구조 실전문제풀이 > 백트래킹' 카테고리의 다른 글

Boj 1799 비숍  (0) 2023.04.18
Boj 2580 스도쿠  (0) 2023.04.16

+ Recent posts