九度oj-1081

题目描述:

给定a0,a1,以及an=p*a(n-1) + q*a(n-2)中的p,q。这里n >= 2。 求第k个数对10000的模。

输入:

输入包括5个整数:a0、a1、p、q、k。

输出:

第k个数a(k)对10000的模。

样例输入:

20 1 1 14 5

样例输出:

8359

代码:

本题最直观的方法是根据公式一步一步求出a(k),如下

#include <stdio.h>

int main (void)
{
    unsigned long long int a,a0,a1,p,q;
    int k;

    while (scanf("%llu%llu%llu%llu%d",&a0,&a1,&p,&q,&k) != EOF)
    {
    if (k == 0) a = a0;
    if (k == 1) a = a1;
    while ( k-1 > 0)
    {
        if (a1 > 10000) a1 %= 10000;
        if (a0 > 10000) a0 %= 10000;
        a = p*a1 + q*a0;
        a0 = a1; a1 = a;
        --k;
    }
    printf ("%llu\n",a % 10000);
    }
    return 0;
}

对于k比较小的情况,这种方法还可以,但是k比较大时就不行了,代码提交上去会返回TLE(超时),这段代码的时间复杂度为O(n),所以我们要想办法把时间复杂度降低。这里有一个比较好的办法就是采用矩阵的方法。以前学习线性代数的时候,对矩阵的认识不够深刻,所以一开始并没有体会到用矩阵会有什么用处。先来看看这个题和矩阵的关系。


(1)


(2)

上面两个公式列出来,应该就会有所启发,会发现矩阵和乘法和数列递推公式之间的关系,那么,转化为矩阵之后有什么好处呢?我们要求一个数列的第n项,如果知道这个数列的通项公式就好了,可是这个递推公式的通项公式却不好求。转化为矩阵运算后,就可以很容易写出a(k),a(k-1)和a(0),a(1)之间的关系:


(3)

这里出现了矩阵M的(k-1)次幂,这对我们是一个启发,这时候就可以用二分法进行计算,我对二分法举一个例子:

M^5 = MMMMM; (5次乘法)

M^5 = ((M^2)^2)*M; (3次乘法)

第二种乘法运算就使用了二分法减少了乘法运算的次数。这样计算M^n算法时间复杂度可以从O(n)降低到O(lgn),这正好符合前面提出的目标。

相应的实现如下:

#include <stdio.h>
#define MOD 10000

// m = m * n
void MatrixMul ( int m[2][2], int n[2][2] )
{
    int s[2][2]={{0,0},{0,0}};
    int i, j, k;
    for(i=0;i<2;i++)
    for(j=0;j<2;j++)
    for(k=0;k<2;k++)
    s[i][j]+=m[i][k]*n[k][j];
    for(i=0;i<2;i++)
    for(j=0;j<2;j++)
    m[i][j]=s[i][j]%MOD;
}

// m = m^n
void MatrixN ( int m[2][2], int n )
{
    int t[2][2]={{m[0][0],m[0][1]},{m[1][0],m[1][1]}};

    if (n==1) return;
    else if (n%2==0) 
    { 
    MatrixN(m,n/2); 
    MatrixMul(m,m); 
    }
    else 
    { 
    MatrixN(m,n-1); 
    MatrixMul(m,t);
    }
}

int main(void)
{
    int a,a0,a1,p,q,k;
    int m[2][2];

    while (scanf("%d%d%d%d%d",&a0,&a1,&p,&q,&k) != EOF)
    {
    if (k==0) a = a0;
    else if (k==1) a = a1;
    else
    {
        m[0][0] = p%MOD; m[0][1] = q%MOD;
        m[1][0] = 1; m[1][1] = 0;
    MatrixN(m,k-1);
    a = m[0][0]*a1+m[0][1]*a0;
    }
    printf ("%d\n", a%MOD);
    }
    return 0;
}

实现很简单,和上面的公式是对应的,就不多说了,从这里我们也可以看出数学工具对思维的巨大帮助,有了矩阵这一工具,可以很大程度上减少思维上的困难,其实用矩阵也是作乘法加法运算,但是如果不使用矩阵,就不容易想像到这样的计算过程。当初学习线性代数的时候,并没有意识到矩阵的妙用,所以需要使用时也不会用。这给我提了一个醒,学习理论,一定要搞明白这种理论有什么用处,不然不算学明白了。而遇到实际的问题想不出来时,一定需要及时补充理论知识。