키타마사법은 다음과 같은 점화식을 가지는 수열의 n번째 항을 빠르게 계산하는 데 사용된다.
an=m∑k=1an−kck
이 점화식을 단순 행렬곱으로 풀면 O(m3logn)의 시간이 필요하지만, 키타마사법을 사용하면 O(m2logn)의 시간이면 충분하다.
~
임의의 an을 다음처럼 a1,a2,…,am에 대한 식으로 표현할 수 있다.
an=m∑k=1akdk
키타마사법의 아이디어는, 위 식을 만족하는 n, dk와 임의의 정수 l에 대하여 다음이 성립한다는 것이다.
an+l=m∑k=1ak+ldk
이 식이 성립하는 이유는 수학적 귀납법으로 쉽게 보일 수 있으므로 생략한다.
이를 통해 a2n에 대한 식을 정리하면 다음과 같다.
a2n=m∑k=1an+kdk=m∑k=1m∑j=1aj+kdjdk
따라서 a2n을 a2,a3,…,a2m에 관한 선형식으로 표현할 수 있고, 이들의 계수는 O(m2) 시간 안에 계산할 수 있다.
또한 am+1,am+2,…,a2m을 a1,a2,…,am에 대한 선형식으로 나타내는 데 O(m2)의 시간이면 충분하다.
이를 종합하면, an에 대한 식을 a2n에 대한 식으로 바꾸는 데 O(m2)의 시간밖에 소요되지 않으므로 O(m2logn)의 시간복잡도로 n번째 항을 계산할 수 있다.
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 | #include <cstdio> const int P = 1000000007; int a[1001]; int c[1001]; int d[1001]; int t[2002]; void Kitamasa( int n, int m) { int i, j; if (n == 1) { d[1] = 1; for (i = 2; i <= m; i++) d[i] = 0; return ; } if (n & 1) { Kitamasa(n ^ 1, m); j = d[m]; for (i = m; i >= 1; i--) d[i] = (d[i - 1] + ( long long )c[m - i + 1] * j) % P; return ; } Kitamasa(n >> 1, m); for (i = 1; i <= m + m; i++) t[i] = 0; for (i = 1; i <= m; i++) for (j = 1; j <= m; j++) t[i + j] = (t[i + j] + ( long long )d[i] * d[j]) % P; for (i = m + m; i > m; i--) for (j = 1; j <= m; j++) t[i - j] = (t[i - j] + ( long long )c[j] * t[i]) % P; for (i = 1; i <= m; i++) d[i] = t[i]; } int main() { int i, n, m, r; scanf ( "%d%d" , &n, &m); for (i = 1; i <= m; i++) scanf ( "%d" , &a[i]); for (i = 1; i <= m; i++) scanf ( "%d" , &c[i]); Kitamasa(n, m); r = 0; for (i = 1; i <= m; i++) r = (r + ( long long )a[i] * d[i]) % P; printf ( "%d" , r); } |
'수학' 카테고리의 다른 글
Dividing Polynomials (0) | 2017.01.12 |
---|---|
Discrete Fourier Transform (0) | 2016.11.17 |
Number Theoretic Fast Fourier Transform (1) | 2016.11.02 |