【矩陣運算+模板】

矩陣運算是屬於線性代數裏的一個重要內容,上學期學完後只覺得矩陣能解線性方程,不過高中的時候聽說過矩陣能優化常係數遞推以及將座標上的點作線性變換,於是找了些資料研究了一下,並把許多經典題以及HDU shǎ崽大牛總結的矩陣乘法的題目[1][2]和開設的矩陣乘法DIY Contest給做完了,感覺收穫頗豐。
    一個矩陣就是一個二維數組,爲了方便聲明多個矩陣,我們一般會將矩陣封裝一個類或定義一個矩陣的結構體,我採用的是後者:

    最特殊的矩陣應該就是單位矩陣E了,它的對角線的元素爲1,非對角線元素爲0。

    若A爲n×p矩陣,B爲p×m矩陣,則它們的乘積AB(有時記做A·B)將是一個n×m矩陣。其乘積矩陣AB的第i行第j列的元素爲:

    一般矩陣乘法採用樸素的O(n^3)的算法:


矩陣加法就是簡單地將對應的位置的兩個矩陣的元素相加:



    在ACM的題目中,我們一般考慮的是n階方陣之間的乘法以及n階方陣與n維向量(把向量看成n×1的矩陣)的乘法。矩陣乘法最重要的性質就是滿足結合律,同時它另一個很重要的性質就是滿足交換率,這保證了矩陣的冪運算滿足快速冪取模(A^k % MOD)算法:

假設k = 27,則k的二進制表示爲11011,所以

,可以看出:k的二進制的每一位矩陣A都要平方,在k二進制爲1的位:末矩陣×平方後的A,在k二進制爲0的位則末矩陣×E(單位矩陣),即不變。代碼如下:



許多題目還要求S = A + A2 + A3 + … + Ak.。其實再作一次二分即可:只需計算log(n)個A的冪即可。

    矩陣在ACM裏用處最大的就是加速常係數遞推方程的計算,最經典的例子就是Fibonacci數列,如果普通的遞推,計算第n項複雜度爲o(n),顯然對於10^9左右的數據就力不從心了。如果用矩陣快速冪遞推的話,計算第n項的複雜度爲o(k3*log(n)),對應一般的題目已經綽綽有餘了,有的題目可以根據矩陣的特殊性進行優化,可以達到o(k2*log(n))。

    由F[n] = F[n-1] + F[n-2],常係數遞推式右邊有兩項,所以向量和矩陣的規格都爲2。容易如下遞推:


    實現計算Fibonacci數列第k項的代碼如下:




轉自http://hi.baidu.com/matrush/blog/item/ed9e7f979287ac037af480c7.html

附上自己的模板:

/**
*注意:這裏只考虐方陣情況
**/
int n;//方陣維數
struct Mat{
    int mat[N][N];
}E;
//初始化單位矩陣
Mat init(){
    Mat E;
    for(int i = 0; i < N; i++){
        for(int j = 0; j < N; j++){
            if(i == j)
            E.mat[i][i] = 1;
            else
            E.mat[i][j] = 0;
        }
    }
    return E;
}
//重載乘法
Mat operator *(Mat a,Mat b){
    Mat c;
    memset(c.mat,0,sizeof(Mat));
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            for(int k = 0; k < n; k++){
                if(a.mat[i][k] && b.mat[k][j]){
                    c.mat[i][j] += a.mat[i][k] * b.mat[k][j];
                }
            }
        }
    }
    return c;
}
//重載加法
Mat operator +(Mat a,Mat b){
    Mat c;
    memset(c.mat,0,sizeof(Mat));
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            c.mat[i][j] = a.mat[i][j] + b.mat[i][j];
        }
    }
    return c;
}
//矩陣快速冪
Mat operator ^(Mat A,int x){
    Mat c;
    c = init();
    for(; x ; x >>= 1){
        if(x&1){
            c = c*A;
        }
        A = A*A;
    }
    return c;
}
//求S = A + A^2 + A^3 + … + A^k
Mat sum(Mat A,int x){
    if(x == 1){
        return A;
    }
    if(x&1){
        return (A^x)+sum(A,x - 1);
    }
    else{
        return sum(A,x/2) + ((A^(x/2)) + E);
    }
}
//求Fibonacci數列第x項(1,1,2,3,5,8....)
int Fib(int x){
    if(x == 1 || x == 2)return 1;
    Mat EE;
    EE.mat[0][0] = 0;
    EE.mat[0][1] = 1;
    EE.mat[1][0] = 1;
    EE.mat[1][1] = 1;
    int beg[2] = {1,
                  1};
    EE = EE^(x - 2);
    int s1 = EE.mat[0][0]*beg[0] + EE.mat[0][1]*beg[1];
    int s2 = EE.mat[1][0]*beg[0] + EE.mat[1][1]*beg[1];
    return s2;
}

另一個模板,使用範圍更廣

struct Mat{
    int n,m;
    int mat[5][5];
    Mat(){
        memset(mat,0,sizeof(mat));
        n = m = 5;
    };
};
Mat operator *(Mat a,Mat b){
    Mat c;
    c = Mat();
    c.n = a.n,c.m = b.m;
    for(int i=1;i<=a.n;i++){
        for(int j=1;j<=b.m;j++){
            for(int k=1;k<=a.m;k++){
                    c.mat[i][j] += (a.mat[i][k]*b.mat[k][j])%MOD;
                    c.mat[i][j] %= MOD;
            }
        }
    }
    return c;
}
Mat operator +(Mat a,Mat b){
    Mat c;
    c = Mat();
    c.n = a.n,c.m = a.m;
    for(int i=1;i<=a.n;i++){
        for(int j=1;j<=a.m;j++){
            c.mat[i][j] = a.mat[i][j]+b.mat[i][j];
        }
    }
    return c;
}
Mat operator ^(Mat a,int k){
    Mat c;
    int i,j;
    c = Mat();
    c.n = a.n,c.m = a.n;
    for(i=1;i<=a.n;i++)c.mat[i][i] = 1;
    while(k){
        if(k&1){
            c = c*a;
        }
        a = a*a;
        k>>=1;
    }
    return c;
}
void out(Mat a){
    int i,j;
    for(i=1;i<=a.n;i++){
        for(j=1;j<=a.m;j++){
            cout<<a.mat[i][j]<<" ";
        }
        cout<<endl;
    }
}



















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章