알고리즘/BOJ 문제풀이

[DP] BOJ 1086 박성원

4Legs 2021. 1. 17. 20:28

문제 링크 : www.acmicpc.net/problem/1086

 

1086번: 박성원

첫째 줄에 정답을 기약분수 형태로 출력한다. p/q꼴로 출력하며, p는 분자, q는 분모이다. 정답이 0인 경우는 0/1로, 1인 경우는 1/1로 출력한다.

www.acmicpc.net

이전에 포스팅했던 BOJ 1176 섞기와 동일한 접근법을 가진다.

어떠한 수들로 순열을 만들었을 때, 그 수들의 순서와 관계없이 우리는 이어붙인 수의 나머지만 알 면 된다.

X개의 수를 이어붙인 큰 수를 k로 나눈 나머지가 r이라면, 그 r에 새로운 수 하나를 붙인 다른 한 수에 대한 나머지가 곧 X + 1개의 수를 이어붙인 큰 수를 k로 나눈 나머지이기 때문이다.

즉 1, 3, 5번의 수를 (3, 1, 5)의 순서로 이어붙이거나 (5, 1 ,3)의 순서로 이어붙였을 때의 나머지가 r로 같다면, (1, 3, 5)번 수를 사용했을 때 나머지가 r인 경우의 수가 2개 존재하는 것과 같다.

총 수의 개수 N이 15로 매우 적으므로, 순열에 사용된 수들의 정보를 비트마스크로 나타낼 수 있다.

즉, 0개의 수를 사용했을 때의 dp[0000..0][0]에서 출발해 각 수들을 사용한 state를 갱신하여 결국 모든 수를 사용하게 되었을 때, 나머지가 0인 상태로 도달할 수 있는 경우의 수가 얼마인지만 세주면 된다.

 

※ (a * b) % k = ((a % k) * (b % k)) % k 임에 유의하자. 이는 새로운 수를 붙였을 때의 나머지를 구하는 데에 필요하다.

[코드]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cmath>
#include <memory.h>
 
using namespace std;
typedef pair<intint> p;
typedef long long ll;
 
int n, k;
ll dp[1 << 16][100];
int remain_cache[16], remain_len[16];
int rcache[51];
vector<string> inputs;
 
int get_remain(string input, int k) {
    int ret = 0;
    for (int i = 0; i < input.size(); i++) {
        ret = (ret * 10 + (input[i] - '0')) % k;
    }
    return ret;
}
 
ll get_dp(int state, int r) {
    //만약 모든 수를 다 사용했을 때, 나머지가 0이 되는지 확인
    if (state == (1 << n) - 1return r == 0 ? 1 : 0;
 
    ll& ret = dp[state][r];
    if (ret != -1return ret;
 
    ret = 0;
 
    for (int i = 0; i < n; i++) {
        //i번째 비트가 현재 state에 포함되어 있는지 확인
        if (state & (1 << i)) continue;
 
        //i번째 비트를 추가한 상태
        int nextstate = state | (1 << i);
 
        //i + 1번째 수를 마지막으로 했을 때, 마지막 수의 나머지
        int remain_last = remain_cache[i + 1];
 
        //i + 1번째 수를 추가했을 때의 전체 나머지
        int target = (r * rcache[remain_len[i + 1]] + remain_last) % k;
 
        //다음 State에 대해 재귀실행
        ret += get_dp(nextstate, target);
    }
 
    return ret;
}
 
void prev_action() {
    //cache 저장
    for (int i = 1; i <= n; i++) {
        int remain = get_remain(inputs[i], k);
        remain_cache[i] = remain;
        remain_len[i] = inputs[i].size();
    }
}
 
void init() {
    string input;
    cin >> n;
 
    memset(dp, -1sizeof(dp));
 
    inputs.push_back("");
    for (int i = 1; i <= n; i++) {
        cin >> input;
        inputs.push_back(input);
    }
    cin >> k;
 
    rcache[0= 1 % k;
    for (int i = 1; i <= 50; i++) rcache[i] = (rcache[i - 1* 10) % k;
}
 
ll get_gcd(ll a, ll b) {
    while (b != 0) {
        ll temp = a % b;
        a = b;
        b = temp;
    }
    return a;
}
 
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(NULL);
 
    init();
    prev_action();
 
    ll answer = get_dp(00);
 
    if (answer == 0) {
        printf("0/1\n");
        return 0;
    }
 
    ll total = 1;
    for (int i = 1; i <= n; i++) total *= i;
 
    if (answer == total) {
        printf("1/1\n");
        return 0;
    }
 
    ll gcd = get_gcd(total, answer);
 
    printf("%lld/%lld\n", answer / gcd, total / gcd);
 
    //system("PAUSE");
    return 0;
}
 
cs