[BOJ/백준] 1086번. 박성원 - C++[cpp]

2024. 9. 7. 15:49·PS/BOJ

문제

 

접근

만약 Bruete Force한 접근 방식으로 모든 경우를 다 구한다고 하면 O(n!)로 주어진 시간 내에 해결이 불가능하다.

어떻게 접근해야 할 지 잘 모르겠지만 분명 매 케이스마다 나머지를 하고 있으면 매우 많은 연산 시간이 필요하다는 것 정도는 알 수 있다. 

(문자열의 최대 길이가 50이기 때문에 특정 상태에서 나머지를 구할 때 해당 문자열 처음부터 끝까지 나머지를 계산하는 것 또한 매우 비효율적이다.)

 

따라서 먼저, 각 문자열의 수를 k로 나눈 나머지를 미리 기록해두자

int cachestr[16];

// 주어진 문자열과 제수(divisor)를 입력받아 나머지를 반환해주는 함수
int mod(const string &s, const int divisor) {
    int result = 0;
    for (int i = 0; i < s.length(); i++) {
        result *= 10;
        result += s[i] - '0';
        result %= divisor;
    }
    return result;
}

// 메인 함수에서
for (int i = 0; i < n; i++) cachestr[i] = mod(arr[i], k);

 

또 반복되서 계산되는 부분이 있는지 생각해보자.

 

특정 수 뒤에 새로운 수가 붙는다고 했을 때, 기존 수의 나머지 값을 알고 있으면 새로 이어진 수의 나머지 값 또한 빠르게 구할 수 있다.

또한, 모든 수는 a * 10^i + b * 10^(i - 1)....형태로 나타낼 수 있다.

==> 문자열의 최대 길이가 50이기 때문에 i가 0부터 50일 때 까지 나눗셈의 나머지 값을 미리 기록해두자.

 

(a × b) % k = [(a % k) × (b % k)] % k 라는 사실로부터, rcache[i] = (rcache[i - 1] * 10) % k라는 사실을 알 수 있다.

int rcache[51];

int main(void) {
	// 중략
    
    rcache[0] = 1;
    for (int i = 1; i < 51; i++) rcache[i] = (rcache[i - 1] * 10) % k;
    
    // ~~~
}

 

숫자를 결합시킬 때 새로운 나머지를 구하는 과정에서 이 rcache 배열을 이용하면 빠르게 구할 수 있다.

 

이제 위에서 봤던 원리를 이용해서 문제 풀이를 완성해보자.

 

이 문제에서 우리가 구해야 하는 것은 k로 나눠 떨어지는 순열의 수 이다. 문자열 N개를 모두 순서대로 나열한다면 TLE가 발생한다.

하지만 우리가 위에서 계산했던 방식을 잘 살펴보면 순열의 순서를 신경쓰지 않고도 원하는 값을 구할 수 있다는 사실을 캐치할 수 있다.

rcache를 구할 때 새로운 수를 붙여가면서 나머지를 기록했다. 이때는 10의 지수승 마다 나머지를 기록해뒀다.

 

만약 10이 아니라 숫자가 붙는다면? ==> 이것 또한 기록해둘 수 있다.

현재 포함되어 있는 문자열의 상태를 비트마스킹을 이용해서 나타내고, 새로운 숫자가 붙어 새로운 문자열을 구성할 때 기존의 데이터를 이용하면 새로운 숫자가 구성됬을 때 빠르게 나머지를 구할 수 있다.

 

for (int mask = 0; mask < (1 << n); mask++) {
        for (int i = 0; i < n; i++) {
            if ((mask & (1 << i)) == 0) {
                int nextMask = mask | (1 << i);
                for (int j = 0; j < k; j++) {
                    int nextK = ((j * rcache[arr[i].length()]) % k + cachestr[i]) % k;
                    dp[nextMask][nextK] += dp[mask][j];
                }
            }
        }
    }

 

현재 집합 상태에서 새로운 수가 추가되었을 때 나머지를 기록해나가는 것이다.

nextK는 새로운 수가 추가되었을 때 k로 나눈 나머지를 의미하고 우리가 위에서 rcache를 구하는 방식과 동일하게 구할 수 있다.

그리고 dp[다음 상태][nextK] 에 현재 상태에 해당하는 수의 개수를 더해준다.

 

최종적으로 dp[111111...][0]이 우리가 구하고자 하는 k로 나눠 떨어지는 순열의 개수가 된다.

 

기약분수를 출력하기 위해서 최대공약수를 <numeric> 헤더 파일에 존재하는 gcd함수를 이용해 구한 뒤 분모 분자에 나눠주면 된다.

 

#include <bits/stdc++.h>
using namespace std;

string arr[16];
int n, k;
long long dp[(1 << 16)][101]; // state, 나머지 r
int rcache[51];
int cachestr[16];

int mod(const string &s, const int divisor) {
    int result = 0;
    for (int i = 0; i < s.length(); i++) {
        result *= 10;
        result += s[i] - '0';
        result %= divisor;
    }
    return result;
}

long long factorial(int n) {
    if (n == 1) return 1;
    return n * factorial(n - 1);
}

int main(void) {
    cin.tie(0)->sync_with_stdio(false);
    cin >> n;
    for (int i = 0; i < n; i++) cin >> arr[i];
    cin >> k;
    dp[0][0] = 1;
    rcache[0] = 1;
    for (int i = 1; i < 51; i++) rcache[i] = (rcache[i - 1] * 10) % k;
    for (int i = 0; i < n; i++) cachestr[i] = mod(arr[i], k);

    for (int mask = 0; mask < (1 << n); mask++) {
        for (int i = 0; i < n; i++) {
            if ((mask & (1 << i)) == 0) {
                int nextMask = mask | (1 << i);
                for (int j = 0; j < k; j++) {
                    int nextK = ((j * rcache[arr[i].length()]) % k + cachestr[i]) % k;
                    dp[nextMask][nextK] += dp[mask][j];
                }
            }
        }
    }
    auto divisor = factorial(n);
    auto dividened = dp[(1 << n) - 1][0];

    long long GCD = gcd(dividened, divisor);

    cout << dividened / GCD << "/" << divisor / GCD;
}

 

'PS > BOJ' 카테고리의 다른 글

[PS/BOJ] 1541번. 잃어버린 괄호 - C++[cpp]  (0) 2025.02.16
[PS/BOJ] 20166번. 문자열 지옥에 빠진 호석 - C++[cpp]  (0) 2024.10.10
[BOJ/백준] 11724번. 연결 요소의 개수 - C++[cpp]  (0) 2024.07.13
[BOJ/백준] 11286번. 절댓값 힙 - C++[cpp]  (0) 2024.06.24
[BOJ/백준] 25501번. 재귀의 귀재 - C++[cpp]  (0) 2024.04.29
'PS/BOJ' 카테고리의 다른 글
  • [PS/BOJ] 1541번. 잃어버린 괄호 - C++[cpp]
  • [PS/BOJ] 20166번. 문자열 지옥에 빠진 호석 - C++[cpp]
  • [BOJ/백준] 11724번. 연결 요소의 개수 - C++[cpp]
  • [BOJ/백준] 11286번. 절댓값 힙 - C++[cpp]
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Spring
        • MVC
        • DB
        • 핵심 원리
        • JPA
      • WEB
        • HTML
        • CSS
        • HTTP
        • Application
      • Computer Science
        • Network
        • Database
        • OS
        • 시스템 프로그래밍
        • 컴퓨터구조
      • Algorithm
        • Divide&Conquer
        • Sort
        • Greedy
        • DP
        • Backtracking
        • NP-Complete
        • Graph
      • Data Structure
        • 자료구조
        • C++ STL
        • Java Collection
      • 소프트웨어 공학
        • 시험 공부 정리
        • Theorem
      • Programming Language
        • Python
        • Java
        • C
        • C++
        • Rust
        • Theory
      • Unix_Linux
        • Common
      • React
      • PS
        • BOJ
        • Tip
        • 프로그래머스
        • CodeForce
      • Book Review
        • Clean Code
      • Math
        • Linear Algebra
      • AI
        • DL
        • ML
        • DA
        • Concepts
      • 우아한테크코스
        • 프리코스
      • Project Review
      • LegacyPosts
      • Android
      • Apple
        • Mac
        • IPhone
        • IPad
      • 모니터
      • Diary
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
lumana
[BOJ/백준] 1086번. 박성원 - C++[cpp]
상단으로

티스토리툴바