https://www.acmicpc.net/problem/24415
24415번: 편지
첫번째 줄에는 공백을 사이에 두고 현수가 작성한 편지의 길이 $N$, 규칙 목록의 길이 $C$, 데미지를 입은 횟수 $K$ 가 정수로 주어진다. $(1 \le N \le 100, 1 \le C \le 10^5, 1 \le K \le 10^{12})$ 두번째 줄에
www.acmicpc.net
이 문제가 생각나기도 합니다.
기본적인 아이디어는 $C$개의 규칙을 모두 합친 것을 $\lfloor \frac{K}{C} \rfloor$ 번 반복하고, $1$번째 규칙부터 $K \;\; \% \;\; C$ 번째 규칙까지 마저 진행한다는 것입니다.
각각의 규칙을 행렬로 나타내는 아이디어는 기본적으로 identity matrix에서 특정 원소만 수정한다고 생각하면 됩니다.
Latex로 보여드리자니 행렬의 사이즈가 좀 커지는 것 같아서, 코드를 참고해주시면 감사하겠습니다.
그리고 $O(C)$개의 규칙을 어떻게 merge하느냐인데, $i$번째 규칙이 $S \;\; a \;\; b$인 경우 $a$번째 row와 $b$번째 row를 swap하고, $A \;\; a \;\; b$인 경우 $a$행의 원소를 적절히 수정해주면 됩니다.
왜냐하면, 이 문제의 규칙을 행렬로 나타낸 것이 $I$와 매우 닮아서 그렇다고 말할 수 있겠습니다. (당연히, 아무 규칙도 없을 때는 $I$입니다.)
그 다음으로 생각해야하는 부분은 이 문제에서 알파벳에 대해 Z 다음이 A라고 정의한 부분입니다. 이로 인해 모듈로를 26으로 설정할 근거가 생깁니다.
이제 시간복잡도를 계산해보면, $C$개의 규칙을 merge할 때 모든 규칙이 swap인 경우 $O(NC)$가 소요됩니다. 따라서, 전체 시간 복잡도는 $O(NC + N^{3} \log K)$입니다.
물론 17401번처럼 생각하면 $O(N^{2}C + N^{3} \log K)$입니다만, 이렇게 하면 시간을 초과해버립니다.
아래는 제 코드입니다.
#include <stdio.h>
#include <string.h>
#define mod 26
typedef struct {
unsigned int array[101][101];
} Matrix;
Matrix I;
Matrix matrix_multiply_modular (Matrix A, Matrix B, int size);
Matrix matrix_power_modular (Matrix A, int size, long long P);
int main() {
int N, C;
long long K;
scanf("%d %d %lld", &N, &C, &K);
char letter[101] = {};
scanf("%s", letter);
Matrix original = {{{}}};
for (int i = 0; i < N; i++) {
original.array[i][0] = letter[i] - 'A';
}
original.array[N][0] = 1;
int size = N + 1;
for (int i = 0; i < size; i++) {
I.array[i][i] = 1;
}
int rem = K % C;
Matrix rule_remainder = I;
Matrix rule_around = I;
unsigned int temp[101];
for (int i = 1; i <= C; i++) {
char Type;
int a, b;
scanf(" %c %d %d", &Type, &a, &b);
if (Type == 'S') {
memcpy(temp, &rule_around.array[a][0], size * sizeof(int));
memcpy(&rule_around.array[a][0], &rule_around.array[b][0], size * sizeof(int));
memcpy(&rule_around.array[b][0], temp, size * sizeof(int));
if (i <= rem) {
memcpy(temp, &rule_remainder.array[a][0], size * sizeof(int));
memcpy(&rule_remainder.array[a][0], &rule_remainder.array[b][0], size * sizeof(int));
memcpy(&rule_remainder.array[b][0], temp, size * sizeof(int));
}
}
else {
rule_around.array[a][N] = (rule_around.array[a][N] + b) % mod;
if (i <= rem) {
rule_remainder.array[a][N] = (rule_remainder.array[a][N] + b) % mod;
}
}
}
Matrix Total_Change = matrix_multiply_modular(rule_remainder, matrix_power_modular(rule_around, size, K / C), size);
Total_Change = matrix_multiply_modular(Total_Change, original, size);
for (int i = 0; i < N; i++) {
printf("%c", Total_Change.array[i][0] + 'A');
}
return 0;
}
Matrix matrix_multiply_modular (Matrix A, Matrix B, int size) {
Matrix result = {{{}}};
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
for (int k = 0; k < size; k++) {
result.array[i][j] += A.array[i][k] * B.array[k][j];
}
result.array[i][j] %= mod;
}
}
return result;
}
Matrix matrix_power_modular (Matrix A, int size, long long P) {
Matrix result = {{{}}};
for (int i = 0; i < size; i++) {
result.array[i][i] = 1;
}
while (P) {
if (P % 2) {
result = matrix_multiply_modular(result, A, size);
}
A = matrix_multiply_modular(A, A, size);
P /= 2;
}
return result;
}
(C11, 56ms, 1512KB, 제출번호 67088919)
시간복잡도를 계산 안 했을 땐 $C$의 크기 때문에 겁을 먹었었는데 다시 보니 별 거 없었습니다.
'분할 정복을 이용한 거듭제곱 > 행렬 거듭제곱' 카테고리의 다른 글
백준 31987번: ESC와 쿼리 (0) | 2024.11.18 |
---|---|
백준 10961번: School canteen (Matrix) (1) | 2023.09.24 |
백준 30165번: Product Oriented Recurrence (0) | 2023.09.24 |
백준 29705번: 문자열 만들기 (0) | 2023.09.23 |
백준 24660번: High Powers (0) | 2023.09.10 |