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

 

22957번: 짝수싫어수

도도의 친구는 짝수를 싫어한다. 어느 정도냐면 짝수만 봐도 불을 뿜으며 빡! 하고 화를 낼 정도이다.  친구를 위해 도도는 짝수싫어수를 고안했다. 짝수싫어수는 $3$, $5$, $7$로 이루어져 있으며

www.acmicpc.net

 

풀이 

더보기

1. DP정의

DP를 다음과 같이 정의하겠습니다.

DP[i][j] : DP[길이가 i인 상황][숫자의 상태가 j]인 상태의 경우의 수

 

위에서 숫자의 상태가 j라는 것은 3, 5, 7의 개수가 짝수인지 아닌지를 의미합니다.

ex)  1002 : 7의 갯수가 짝수, 5의 개수가 짝수, 3의 개수가 홀수인 상태

.     1012 : 7의 갯수가 홀수, 5의 개수가 짝수, 3의 개수가 홀수인 상태

 

 

i는 N이 30까지 나오기때문에 30까지 나올 것이고

j는 경우의 수가 0~7까지 이므로 8까지 나올 것입니다.

DP[30][8] 크기의 상태 DP를 만들 수 있습니다.

 

 

DP[i][j]의 다음 상태는 DP[i - 1][j^1], DP[i - 1][j^2], DP[i - 1][j^4]로 정의할 수 있고

(3,5,7의 개수가 변하는 것을 고려한 것입니다.)

 

 

점화식을 다음과 같이 정의할 수 있습니다.

DP[i][j] = DP[i-1][j^1] + DP[i-1][j^2] + DP[i-1][j^4];

 

점화식을 이용해서 DP 테이블을 채우면 됩니다.

 

 


2. DP를 사용하는 방법

DP를 구했지만 DP를 이용해서 답을 구하는 방법을 생각해봐야 합니다.

 

예시를 통해서 방법을 설명하겠습니다.

N = 6, K = 400인 상태에서 정답을 구하는 방법입니다.

정답으로 나올 수 있는수는  10^6이하인 수입니다. 최대 6자리까지 나올 수 있기 때문에 다음과 같이 표현하겠습니다.

 

정답을 구하기 위해서 해당 빈칸에 숫자를 넣을 수 있는지 확인해야 합니다.

그러기 위해서는 맨 앞자리가 특정 숫자일 경우 나올 수 있는 경우의 수를 알아야 되고 

구하는 방법은 다음과 같습니다.

int cnt[3] = { 0, };
for (int k = 0; k < 3; k++)
	for (int j = 0; j < 8; j++)
		if (fn != j)
		    cnt[k] += Dp(i - 1, j ^ (1 << k));
cnt[k] : 맨앞자리가 k번째 숫자인 상태의 경우의 수
fn : 현재 상태에서 도달하면 안되는 상태

 

처음은 아무 숫자도 배치하지 않았기 때문에 fn = 0002인 경우는 제외해야 합니다.

fn = 0002인 상태로 cnt를 갱신해주고 아래의 순서대로 배치를 할 수 있는지 검사합니다.

 

먼저 맨앞자리에 7이 올 수 있는지 부터 판단하겠습니다.
cnt[0]은 현재 맨앞자리가 7인 경우의 수를 의미합니다.
여기선 cnt[0] = 182이므로 K보다 작습니다. (cnt[0] < K) (K = 400)
7은 맨 앞에 둘수 없습니다.
K -= cnt[0]를 해줍니다. (k = 218)


맨앞에 5가 올 수 있는지 검사합니다.
cnt[1]은 현재 맨앞자리가 5인 경우의 수를 의미합니다.
cnt[1] = 182이므로 K보다 작습니다. (cnt[1] < K) (K218)
5은 맨 앞에 둘 수 없습니다.
K -= cnt[1]를 해줍니다. (k = 36)


마찬가지로 3이 맨 앞에 올 수 있는지 검사합니다.
cnt[2]도 현재 맨앞자리가 3인 경우의 수를 의미합니다.
cnt[2] = 182입니다. K보다 큽니다. (cnt[1] > K) (K = 36)
3은 맨앞에 둘 수 있습니다.


(만약 어떠한 숫자도 배치할 수 없으면 배치하지않고 넘어갑니다.)

 

 

3을 배치했으니 fn을 갱신해야 됩니다.

3이 홀수 5가 짝수 7이 짝수인 경우인 경우를 제외해야 합니다.

fn을 1002로 갱신해줍니다 모든 자릿수마다 해당 방법으로 어떤 숫자가 와야 되는지 구해준다면

정답을 구할 수 있습니다.

 

코드

더보기
#include <bits/stdc++.h>

using namespace std;

#define endl "\n"
#define int long long
#define float double

using namespace std;

int DP[32][10];
int Dp(int n, int bit)
{
	if (n == 0)
	{
		if (bit == 0)
			return 1;
		else
			return 0;
	}

	if (DP[n][bit] != -1)
		return DP[n][bit];
	DP[n][bit] = 0;
	DP[n][bit] += Dp(n - 1, bit ^ 1) + Dp(n - 1, bit ^ 2) + Dp(n - 1, bit ^ 4);
	return DP[n][bit];
}

int N, K;
string Num[3] = { "7","5","3" };
int32_t main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	std::cout.tie(NULL);

	memset(DP, -1, sizeof(DP));

	cin >> N >> K;

	int b = 0;

	int fn = 0;
	for (int i = N; i >= 1; i--)
	{
		int cnt[3] = { 0, };
		for (int k = 0; k < 3; k++)
			for (int j = 0; j < 8; j++)
				if (fn != j)
					cnt[k] += Dp(i - 1, j ^ (1 << k));

		for (int j = 0; j < 3; j++)
		{
			if (K > cnt[j])
				K -= cnt[j];
			else
			{
				cout << Num[j];
				fn ^= (1 << j);
				break;
			}
		}
	}

	cout << endl;
}

 

'알고리즘 > 문제풀이' 카테고리의 다른 글

백준 16359 Disks Arrangement  (0) 2021.12.01
백준 17274 카드 공장 (Large)  (0) 2021.09.18
백준 22359 흔한 타일 색칠 문제  (0) 2021.08.11
백준 22348 헬기 착륙장  (0) 2021.07.29
백준 13433 기하 문제  (4) 2021.07.14

+ Recent posts