https://www.acmicpc.net/problem/9556
9556번: 수학 숙제
각 테스트 케이스마다 조건을 만족하는 N자리 정수의 개수를 1,000,000,007로 나눈 값을 출력한다.
www.acmicpc.net
처음엔 포함 배제로 풀려고 했다가, 제가 포함 배제에 익숙하지 못해서 구현을 포기하고 다른 방법으로 풀게 됐습니다.
이 문제의 첫 번째 포인트는 $N$자리 수는 한 개 이상의 0으로 시작할 수 있다는 것입니다. 즉 우리가 체크해야 할 수의 범위는 0부터 $10^{N}-1$까지입니다.
두 번째 포인트는 이 문제의 나눠 떨어진다는 표현은 나머지가 0이라는 것입니다. 그래서 0은 모든 양의 정수로 나누어 떨어지는 수라고 합니다.
이 문제는 물론 포함 배제로도 풀 수 있겠지만, 더 쉬운 관찰은 다음과 같습니다.
문자열 $S$를 정수 $x$가 만족한다면, $x+60$ 또한 같은 조건을 만족한다.
이는 $\textrm{LCM} \; (1,2,3,4,5,6) = 60$임을 이용하면 쉽게 알 수 있습니다.
결론적으로, $0$부터 $59$까지의 정수에 대해 문자열을 만족하는 정수가 몇 개인지 구하고, 이게 몇 주기 동안 반복되는지를 구하면 문제가 끝나게 됩니다.
이제 주기의 수를 구하는 함수를 구현하는 방법에 대해 설명하겠습니다. 이 링크에 따르면, 정수 나눗셈의 몫이 다음과 같습니다.
$$ \lfloor \frac{x}{k} \rfloor = \frac{x - \; x \; \textrm{mod} \; k}{k}$$
이 식을 이용하면, $10^{10^{18}}$ 을 60으로 나눈 몫을 $10^{9}+7$로 나눈 나머지 같은 값을 쉽게 구할 수 있습니다. (혹은 더 쉬운 방법이 있으면 알려주시면 감사하겠습니다)
시간 복잡도는 역수를 구하는 데 $O (\log P)$ 이므로 전체 복잡도 역시 $O (\log P)$ 입니다. 다음은 제 코드입니다.
#include <stdio.h>
#define mod 1000000007
long long N;
char string[7];
long long check (int x); // 0 <= x < 60
long long power_modular (long long x, long long y, long long M);
long long floor_modular (long long x, long long y);
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%lld %s", &N, string);
long long temp = 0;
for (int x = 0; x < 60; x++) {
temp += check(x);
}
long long ttemp = 0;
long long residue = (power_modular(10, N, 60) + 59) % 60;
for (long long x = 0; x <= residue; x++) {
ttemp += check(x);
}
long long answer = (floor_modular(N, 60) * temp + ttemp) % mod;
printf("%lld\n", answer);
}
return 0;
}
long long check (int x) {
for (int i = 0; i < 6; i++) {
if (string[i] == '2') {
continue;
}
else if (string[i] == '1') {
if (x % (i + 1) != 0) {
return 0;
}
}
else {
if (x % (i + 1) == 0) {
return 0;
}
}
}
return 1;
}
long long power_modular (long long x, long long y, long long M) {
long long result = 1;
while(y) {
if (y % 2) {
result = result * x % M;
}
x = x * x % M;
y /= 2;
}
return result;
}
long long floor_modular (long long x, long long y) {
// floor [ ( 10^x - 1 ) / y ]
long long inv_y = power_modular (y, mod - 2, mod);
long long temp1 = (power_modular(10, x, mod) + mod - 1) % mod;
long long temp2 = (power_modular(10, x, y) + y - 1) % y;
return (temp1 - temp2 + mod) * inv_y % mod;
}
(C11, 0ms, 1116KB, 제출번호 54828255)
'분할 정복을 이용한 거듭제곱 > 정수 거듭제곱' 카테고리의 다른 글
백준 27299번: 헌내기 현철 (0) | 2023.02.12 |
---|---|
백준 14679번: 영우와 '갓4' (0) | 2023.02.12 |
백준 16201번: 발코니 공사 (1) | 2023.01.26 |
백준 26132번: Fair Division (1) | 2023.01.24 |
백준 16214: N과 M (0) | 2023.01.24 |