문제 : https://www.acmicpc.net/problem/1086


DP Bitmasking 비트마스킹

● 최대 길이가 50인 N개의 정수들을 뽑는 경우는 N! 개가 존재한다. N이 최대 15이므로, N! 개를 모두 구하면 시간초과 발생 --> DP 로 문제를 접근

● DP[state][r] := 현재 어떤 정수들을 뽑았는지 state에 비트마스킹을 이용해 표시하고, r 은 K로 나눴을 때 나머지를 의미한다.

 즉, DP[state][r] := state 상태에서 K로 나눴을 때 나머지가 r인 경우의 수

 

DP 배열을 채우기 위해 다음과 같은 정의가 미리 필요하다.

● mul[i] := pow(10, i) % K 를 미리 다 저장해둔다. mod 연산이므로 쉽게 계산 가능

● integer[i] := ( N개의 정수 중 i번 째 정수 ) % K 를 mul[] 을 이용해 미리 계산해놓는다.

 

그럼 DP[ i ] [ j ] 는 다음과 같이 채울 수 있다.

 현재 상태 ( i ) 와 나머지 j 에 대해서 N개의 정수를 다 뒤에 붙여본다. 이때, 이미 현재 상태에서 뽑아서 사용한 정수는 붙여주지 않는다.

 따라서, N개의 정수 중 k 번 째 정수를 뽑았다고 가정하면, i & ( 1 << k ) == 0 이 성립하면 그 정수를 붙여주고, 아니면 이미 사용한 정수이므로 넘어간다.

 이때, 정수를 붙여준다는 것은 현재 상태에서의 나머지 j 를 k번 째 정수의 길이만큼 옆으로 밀어주고, k번 째 정수를 붙여주는 것이므로 j * 10^(k번 째 정수의 길이)를 해주면 되는데, 미리 계산해놓은 mul 배열을 이용해 연산해주고, 뒤에 k번 째 정수를 붙여주는 연산은 integer 배열을 이용하면 된다.

 예시를 통해 익혀보면 현재 "12345" 를 만들었고, "678" 을 붙여주는 연산을 생각해보면 "12345" * 1000 + "678" 을 생각하면 된다.

 이때, 정수론에 의해 ("12345" * 1000 + "678") mod K == ("12345" * 1000) mod K + "678" mod K 와 동일하고, 더 쪼개면 == ("12345" mod K) * (1000 mod K) + ("678" mod K) 로 분해할 수 있다. "12345" mod K 는 우리가 DP[i][j] 에서 j에 해당되는 값이고, (1000 mod K) 는 mul 배열에서 다 계산했고, ("678" mod K) 도 integer 배열에 미리 다 연산해놓았으므로 쉽게 계산할 수 있다.

피드백

 처음에 DP 배열 정의까지는 떠올렸으나, DP 배열 초기화 및 채우는 방법에서 굉장히 오래 헤맸다.

 DP[i][j] 에 그냥 1 ~ N 번 째 정수를 다 붙여본다고 생각하면 간단한데 너무 꼬아서 생각한 것 같다.

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;

int N, K;
long long dp[1 << 15][100];
long long factorial[16];
int integer[15];
int mul[51];
string list[15];

long long gcd(long long a, long long b) {
	while (b) {
		long long r = a % b;
		a = b;
		b = r;
	}
	return a;
}

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

	cin >> N;
	for (int i = 0; i < N; i++)
		cin >> list[i];
	cin >> K;

	for (int i = 0; i < N; i++)
		reverse(list[i].begin(), list[i].end());

	factorial[1] = 1;
	for (int i = 2; i <= 15; i++)
		factorial[i] = factorial[i - 1] * i;

	mul[0] = 1;
	for (int i = 1; i < 51; i++)
		mul[i] = (mul[i - 1] * 10) % K;

	for (int i = 0; i < N; i++) {
		int temp = 0;
		for (int j = 0; j < list[i].length(); j++) {
			temp += ((list[i][j] - '0') * mul[j]) % K;
			temp %= K;
		}
		integer[i] = temp;
	}

	// dp 배열을 채워보자.
	dp[0][0] = 1;
	for (int i = 0; i < (1 << N); i++) {
		for (int j = 0; j < K; j++) {
			for (int k = 0; k < N; k++) { // k번 째 친구를 안쓴애들만 쓰자.
				if ((i & (1 << k)) == 0) { // 현재 상태랑 k 사용이랑 겹치는게 없음.
					// 현재 나머지가 j --> k번 째 수를 사용하면 나머지가 바뀜
					int next = j * mul[list[k].length()];
					next %= K;
					next += integer[k];
					next %= K;
					dp[i | (1 << k)][next] += dp[i][j];
				}
			}
		}
	}

	long long g = gcd(dp[(1 << N) - 1][0], factorial[N]);
	cout << (dp[(1 << N) - 1][0] / g) << '/' << (factorial[N] / g) << '\n';

	return 0;
}

 

'Problem Solving > BOJ' 카테고리의 다른 글

[BOJ] 2352 : 반도체 설계  (0) 2020.06.15
[BOJ] 1162 : Revamping Trails  (0) 2020.06.09
[BOJ] 7575 : 바이러스  (2) 2020.05.22
[BOJ] 11559 : Puyo Puyo  (0) 2020.05.21
[BOJ] 1038 : 감소하는 수  (0) 2020.05.20

+ Recent posts