백준

백준 17136번 색종이 붙이기 (c++)

딴짓거리 2025. 1. 24. 16:00

풀이도 구현도 무난하게 쉬워서 골드 3정도 되는 문제 아닌가 싶었는데

종료조건을 잘 생각해야 시간초과를 면할 수 있는 문제였다.

 

 

vector<vector<int>> mp;
vector<int> paper(6, 0);

int ans = 210000000;
int cntOne = 0;

10x10 종이의 상태를 저장할 배열

각 크기의 색종이 가 몇개 쓰였는지 저장할 배열

정답을 저장할 변수

재귀함수 종료를 빠르게 판단 할 수 있도록 색종이 를 붙여야 할 갯수를 저장 할 변수

 

bool isComplete(vector<vector<int>> &arr)
{
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			if (arr[i][j] == 1) return false;
		}
	}
	return true;
}

만들어진 종이판이 정답으로 적합한지 판단한다.

색종이 를 붙여야 할 칸이 한칸이라도 남아있으면 실패

 

bool isStick(vector<vector<int>> &arr, int x, int y, int n)
{
	for (int i = y; i < y + n; i++)
	{
		for (int j = x; j < x + n; j++)
		{
			if (i < 0 || i >= 10 || j < 0 || j >= 10) return false;
			if (arr[i][j] != 1) return false;
		}
	}
	return true;
}

vector<vector<int>> OnStick(vector<vector<int>> arr, int x, int y, int n)
{
	for (int i = y; i < y + n; i++)
	{
		for (int j = x; j < x + n; j++)
		{
			arr[i][j] = 2;
		}
	}
	return arr;
}

 

순회하며 색종이 를 붙여야 할 칸에 n크기의 색종이 를 붙일수 있는지 판단하고

판에 실제로 붙인다.

 

void sol(vector<vector<int>> arr,int lv, int cnt)
{
	if (lv >= ans) return;
	if (cnt == 0)
	{
		if (isComplete(arr) == true)
		{
			ans = min(ans, lv); 
			return;
		}
	}
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			if (arr[i][j] == 1)
			{
				for (int k = 5; k > 0; k--)
				{
					if (paper[k] >= 5)continue;
					if (isStick(arr, j, i, k) == true)
					{
						paper[k]++;
						sol(OnStick(arr, j, i, k), lv + 1, cnt - k * k);
						paper[k]--;
					}
				}
				return;
			}
		}
	}
}

재귀 레벨은 색종이를 몇개 붙였는지 나타내는것이다.

즉 현재 저장된 최소 색종이 의 갯수보다 더 깊이 들어가려는 것 같으면 의미 없는 연산이므로 반환

 

색종이 를 붙여야 할 칸의 수 cnt가 0이 되면 현재 종이의 상태를 검사한 후

모든 칸이 적절하면 정답을 갱신해준다.

 

종이의 모든 칸을 순회하며 색종이 를 붙여야할 칸(1)을 만나면 각종 크기의 색종이를 붙여본다.

한 종류의 색종이가 5개를 초과하지 않도록 주의한다.

붙여도 된다고 판단되면 종이의 카운트를 올려주고, 색종이를 붙여주고, 재귀에 들어간다.

 

여기서 색종이를 12345다 대봤으면 다음칸으로 이동할 필요 없이 바로 리턴해주는것이 중요하다.

색종이 를 한장이라도 붙였으면 다음 재귀에서 다음칸을 검사할 것이므로 현재 재귀에서 다음칸으로 이동하는 의미는 없다.

또한 어떤 크기의 색종이도 붙이지 못했다면 해당 칸은 채울 방법이 없기에 그 판 자체가 허수가 되어 더이상 진행할 의미도 없어진다.

 

signed main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	for (int i = 0; i < 10; i++)
	{
		vector<int> tmp;
		for (int j = 0; j < 10; j++)
		{
			int a;
			cin >> a;
			if (a == 1) cntOne++;
			tmp.push_back(a);
		}
		mp.push_back(tmp);
	}
	if (cntOne == 0)
	{
		cout << 0 << endl;
		return 0;
	}
	

	sol(mp, 0, cntOne);
	if (ans == 210000000)
	{
		cout << -1 << endl;
	}
	else cout << ans << endl;
	
}

스티커를 붙여야 할 칸이 한칸도 없으면 바로 0을 출력하고 리턴,

정답 변수가 초기값 그대로 나왔으면 스티커를 붙이는게 불가능하다는 뜻이므로 -1 출력