풀이
[문제 풀이]
이 문제는 매우 큰 수를 입력받아 처리를 해야하는 문제다.
이외에도 N이 15로 큰 값이기 때문에 (15!)을 전부 계산하면 시간 초과가 나게 된다.
그래서 이러한 반복을 줄이는 방법은 나머지를 이용하는 방법이다.
예를 들어 아래와 같은 경우를 생각해보자.
그러면 17, 6 순서로 정수를 만드는 경우와 6, 17 순서로 정수를 만드는 경우는 나머지가 1로 같다.
이를 이용하면 176333과 617333의 나머지가 같음을 알 수 있다.
(왜냐하면 176과 617의 나머지가 1이였으므로, 333을 뒤에 붙이면 1333 이 된다. 이때, 두 수가 1333으로 같으므로 두 경우는 같은 경우다.)
이를 이용하면 [현재 사용한 수열의 수 집합][남은 나머지]의 2차원 vector를 이용해 빠르게 문제를 해결할 수 있다.
여기서 현재 사용한 수열의 집합은 bit 마스킹을 이용해 2^N의 수로 표현했다.
+ 추가로 고려해야하는 부분이 있다.
나머지와 N개의 수를 이용해 K로 나눈 나머지를 계속 계산하는 과정에서 시간 초과가 날 수 있다.
이 부분도 2차원 vector로 계산값을 기록해 해결할 수 있다.
[현재까지 계산된 나머지][N번째 수]로 이루어진 2차원 vector를 만들면 단순히 2차원 배열의 값을 찾는 것으로 계산을 하지 않고 이후 나머지가 얼마가 되는지 빠르게 계산할 수 있다.
(여기서 현재까지 계산된 나머지는 K로 나눈 수라서 K값 보다 작다)
이후 두 수를 기약분수로 만들어 주면 되는데, 기약분수로 만들어주는 과정은 GCD를 이용해 최대 공약수를 계산해 나누어 주면 된다.
[아이디어 정리]
- [현재 사용한 수열의 수 집합][집합으로 수를 만든 후 남은 나머지] 의 2차원 vector를 만들어 같은 경우는 중복된 계산을 하지 않도록 한다.
- 현재 나머지 + i번째 수로 수를 만들 경우 나머지 값을 중복해서 계속 계산하지 않도록 [현재까지 계산된 나머지][N번째 수]로 이루어진 2차원 vector를 만들고 이미 계산된 값을 이용해 계산한다.
- 최종적으로 경우의 수가 계산되면, GCD를 이용해 최대공약수를 구하고 최대공약수로 나눈 값을 출력한다.
Code
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int N, K;
vector<vector<long long>> DP;
vector<char[50]> CV;
vector<vector<int>> QUO; // [나머지][몇번째 수]
int CtoI(char &c) {
int ret = c - '0';
return ret;
}
int CalcNum(int quo, int cN) {
int nNum;
for (int i = 0; i < 50; i++)
{
nNum = CtoI(CV[cN][i]);
if (nNum >= 0 && nNum <= 9) {
quo *= 10;
quo += nNum;
quo %= K;
}
else {
break;
}
}
return quo;
}
long long DFS(int bits, int quo) {
long long* retAns = &DP[bits][quo];
if (DP[bits][quo] != -1)
{
return DP[bits][quo];
}
int idx;
DP[bits][quo] = 0;
for (int i = 0; i < N; i++) {
idx = 1 << i;
if ((bits & idx) == 0)
{ //방문한 적이 없다면, 방문시키기
DP[bits][quo] +=DFS(bits | idx, QUO[quo][i]);
}
}
return DP[bits][quo];
}
long long GCD(long long a, long long b) {
if (a % b == 0) {
return b;
}
else {
return GCD(b, a % b);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> N;
CV = vector<char[50]>(N);
for (int i = 0; i < N; i++) {
cin >> CV[i];
}
cin >> K;
// 초기화
QUO = vector<vector<int>>(K,vector<int>(N,-1));
for (int i = 0; i < K; i++) {
for (int j = 0; j < N; j++) {
QUO[i][j] = CalcNum(i, j);
}
}
int maxBits = (1 << N) - 1;
DP = vector<vector<long long>>(maxBits + 1, vector<long long>(K, -1));
DP[maxBits][0] = 1;
for (int i = 1; i < K; i++) {
DP[maxBits][i] = 0;
}
long long G = 1;
for (int i = 1; i <= N; i++) {
G *= i;
}
if (DFS(0, 0) == 0) {
cout << "0/1";
return 0;
}
long long gcd = GCD(G, DFS(0, 0));
cout << DFS(0, 0) / gcd << "/" << G / gcd << "\n";
return 0;
}
처음 문제를 봤을 때, 작은 경우들 중 같은 경우가 있다는게 바로 보이지 않아 나머지를 이용할 생각이 나지 않았다.
예전부터 느꼈지만 이런 유형의 문제는 기준으로 잡을 부분이 바로 생각나면 빠르게 해결되지만, 생각이 안나면 정말 오래 걸리는 문제인 것 같다.
https://www.acmicpc.net/problem/1086
'Algorithm > BAEKJOON' 카테고리의 다른 글
[백준/C++] RGB거리 2 (No. 17404) (1) | 2024.07.01 |
---|---|
[백준] Solved.ac 랜덤 마라톤 (No. 6903, 11896, 15410, 11501, 9335, 6207, 2467, 12796) (0) | 2024.06.28 |
[백준/C++] 외판원 순회 (No. 2098) (0) | 2024.06.24 |
[백준/C++] 집합 (No. 11723) (0) | 2024.06.23 |
[백준/C++] 최고의 초콜릿 성지순례 (No. 31461) (0) | 2024.06.21 |