1. 程式人生 > 其它 >矩陣乘法,矩陣快速冪學習筆記

矩陣乘法,矩陣快速冪學習筆記

矩陣乘法和矩陣快速冪是noip裡面經常考到的東西,可以用來優化一些dp,

例如菲波那契數列或者當前這一維於上一維所有狀態都有關係的dp

通常這些題目都有一個技巧,加入你的暴力dp式子推出來之後長得類似與f[ i ][ j ]=sum(f[ i-1 ][ p ]*g[ p ][ j ])

並且這個g陣列是一個固定的,並且大小不是很大的,可以預先處理出來的,那麼我們可以考慮運用矩陣快速冪進行優化。

那我們先從最基本的矩陣乘法開始入手,

我們定義3個矩陣 a,b,c;

a和b就是我們要進行想乘的兩個矩陣,c是我們的答案矩陣;

那麼這個矩陣乘法的狀態轉移式就是  c[ i ][ j ]=sum(a[ i ][ k ]*b[ k ][ j ]);

那麼貼個最普通也是用的範圍最廣的板子吧

 1 struct matrix
 2 {
 3     int a[maxn][maxn];
 4 }x,y;
 5 int n,m;
 6 matrix cheng(matrix a,matrix b)
 7 {
 8     matrix c;
 9     memset(c.a,0,sizeof(c.a));
10     for(int i=1;i<=a矩陣第一維;i++)
11     {
12         for(int k=1;k<=a矩陣第二維和b矩陣第一維;k++)
13         {
14             for
(int j=1;j<=b矩陣的第二維;j++) 15 { 16 c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%m; 17 } 18 } 19 } 20 return c; 21 }
矩陣乘法

那麼加入要乘的次數太多,肯定是要t的,所以我們可以利用一個普通的數字快速冪的做法,將數字相乘,換成矩陣相乘

程式碼實現其實跟普通的數字快速冪一樣的,只不過需要讓這個temp矩陣與a的第一次相乘結果為a,只需要把temp的整個第i維第i個賦值為1就好了

 1 matrix qpow(matrix a,int n)
 2 {
 3     matrix b;
 4     memset(b.a,0,sizeof(b.a));
 5     for(int i=1;i<=a矩陣最長的那一維;i++)//先定義一個矩陣,使他第一次乘等於他自己
 6     {
 7         b.a[i][i]=1;   
 8     }
 9     while(n)
10     {
11         if(n&1)
12         {
13             b=cheng(b,a);
14         }
15         a=cheng(a,a);
16         n>>=1;
17     }
18     return b;
19 }
快速冪

接下來講幾個題目吧:

1.菲波那契第N項

菲波那契的公式應該都知道吧就是f[ i ]=f[ i-1 ]+f[ i-2 ];

如果你一個一個來網上遞退,那麼肯定是會時空雙爆的,我們考慮矩陣快速冪進行優化;

但其實,我們是可以發現dp的規律的,定義f[ i ][ 1 ]為第f[ i ]個菲波那契,f[i][2]為i-1項,

那麼轉移式就是:f[ i ][ 1 ]=f[ i-1 ][ 1 ]+f[ i-1 ][ 2 ],f[ i ][ 2 ]=f[ i-1 ][1];

這貌似還沒有滿足矩陣快速冪的優化條件,因為還少一個數組,那麼我們應該如何做呢?

我們如果把加上變成加它乘以1,那麼這個表示式子就可以變成:

f[ i ][ 1 ]=f[ i-1 ][ 1 ]*1+f[ i-1 ][ 2 ]*1,f[ i ][ 2 ]=f[ i-1 ][1]×1+f[ i-1 ][ 2 ]*0;

我們可以根據這個定義一個數組:g[1][1]=1,g[1][2]=1,g[2][1]=1,g[2][2]=0;

再次轉移:f[ i ][ 1 ]=f[ i-1 ][ 1 ]*g[ 1 ][ 1 ]+f[ i-1 ][ 2 ]*g[ 2 ][ 1 ],

     f[ i ][ 2 ]=f[ i-1 ][ 1 ]*g[ 1 ][ 2 ]+f[ i-1 ][ 2 ]*g[ 2 ][ 2 ];

發現規律了嗎????

這不就是矩陣快速冪優化dp的通用公式嗎????

那麼這個題就是f[1][1]=1,f[1][2]=1;的2×2的矩陣×g這個矩陣的(n-2)次冪,而最後的f[ 1 ][ 1 ]就是最後的答案!!

為啥是n-2次呢?

f[ 1 ][ 1 ]=1,f[ 1 ][ 2 ]=1時已經算到第2個了,你要算到第n個,那麼還需要算n-2次

程式碼?我都說到這分子上了,還需要我跟你貼程式碼???

還是貼一個吧

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 inline int read()
 5 {
 6     int x=0,f=1;
 7     char ch=getchar();
 8     while(ch<'0'||ch>'9')
 9     {
10         if(ch=='-')
11         f=-1;
12         ch=getchar();
13     }
14     while(ch>='0'&&ch<='9')
15     {
16         x=(x<<1)+(x<<3)+(ch^48);
17         ch=getchar();
18     }
19     return x*f;
20 }
21 struct matrix
22 {
23     int a[3][3];
24 }x,y;
25 int n,m;
26 matrix cheng(matrix a,matrix b)
27 {
28     matrix c;
29     memset(c.a,0,sizeof(c.a));
30     for(int i=1;i<=2;i++)
31     {
32         for(int k=1;k<=2;k++)
33         {
34             for(int j=1;j<=2;j++)
35             {
36                 c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%m;
37             }
38         }
39     }
40     return c;
41 }
42 matrix qpow(matrix a,int n)
43 {
44     matrix b;
45     for(int i=1;i<=2;i++)
46     {
47         for(int j=1;j<=2;j++)
48         if(i==j)
49         b.a[i][j]=1;
50         else
51         b.a[i][j]=0;
52     }
53     while(n)
54     {
55         if(n&1)
56         {
57             b=cheng(b,a);
58         }
59         a=cheng(a,a);
60         n>>=1;
61     }
62     return b;
63 }
64 signed main()
65 {
66     n=read(),m=read();
67     x.a[1][1]=1;
68     x.a[1][2]=1;
69     x.a[2][1]=0;
70     x.a[2][2]=0;
71     y.a[1][1]=y.a[2][1]=y.a[1][2]=1;
72     y.a[2][2]=0;
73     y=qpow(y,n-2);
74     x=cheng(x,y);
75     printf("%lld",x.a[1][1]);
76 }
1

2.佳佳的菲波那契

給你一個新的定義s[ i ]=sum( f[ j ] )( 0<j<=i )

讓你求t[ i ]=sum( j*f[ j ] )( 0<j<=i )

菲波那契?這個我會,仔細看看?

這啥玩意,讓我求t還給我定義個s?

那麼這個題目肯定是有技巧的,當然,矩陣快速冪求菲波那契那都是基本操作了;

那麼我們考慮對s先進行化簡:s[n]=f[1]+----f[n]+1-1=f[1]+f[2]+f[2]----f[n]-1=f[n]+f[n+1]-1;

最後就是f[ n+2 ]-1,呀,這好像有點意思啊

那我們考慮對t進行化簡:

t[n]=n*f[n]+-------f[1];
t[n]=n*s[n]-(s[1]------s[n-1]);
t[n]=n*(f[n+2]-1)-(f[3]-----f[n+1]-(n-1))
t[n]=n*(f[n+2]-1)-(s[n+1]-(n-1)-2)
t[n]=n*f[n+2]-n-(f[n+3]-n-2)
t[n]=n*f[n+2]-f[n+3]+2

那麼最後直接出結果了好吧

得得得,看到這裡,如果你還不會做的話你可以扔掉你的腦子了

程式碼:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
        f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
struct matrix
{
    int a[3][3];
}x,y;
int n,m;
matrix cheng(matrix a,matrix b)
{
    matrix c;
    memset(c.a,0,sizeof(c.a));
    for(int i=1;i<=2;i++)
    {
        for(int k=1;k<=2;k++)
        {
            for(int j=1;j<=2;j++)
            {
                c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%m;
            }
        }
    }
    return c;
}
matrix qpow(matrix a,int n)
{
    matrix b;
    for(int i=1;i<=2;i++)
    {
        for(int j=1;j<=2;j++)
        if(i==j)
        b.a[i][j]=1;
        else
        b.a[i][j]=0;
    }
    while(n)
    {
        if(n&1)
        {
            b=cheng(b,a);
        }
        a=cheng(a,a);
        n>>=1;
    }
    return b;
}
signed main()
{
    n=read(),m=read();
    x.a[1][1]=1;
    x.a[1][2]=1;
    x.a[2][1]=0;
    x.a[2][2]=0;
    y.a[1][1]=y.a[2][1]=y.a[1][2]=1;
    y.a[2][2]=0;
    y=qpow(y,n+1);
    x=cheng(x,y);
    int a=x.a[1][1],b=x.a[1][2];
    printf("%lld",(b*n%m-a+2+m)%m);
}
2

3迷路scoi2009

這題,寧是否看出來如何使用矩陣快速冪?

如果寧翻到這裡了,那麼應該是看不出來了,那麼我來告訴你!!!!!

首先,我們先假設每個點的邊權是1那麼整個矩陣就是一個0 1矩陣對吧;

那麼我們可以想出一個dp暴力吧,

定義f[ i ][ j ][ k ]為花費k個體力從i到j的方案數,那麼狀態轉移方程就可以是

f[ i ][ j ][ k ]=sum(f[ i ][ p ][ k-1 ]*g[ p ][ j ])

我去,怎麼感覺有點眼熟啊。。。

已經很明顯了,我們把體力那一維度去掉,再仔細一考慮,這個矩陣k次方後的f[ i ][ j ]不就是i到j花費k個體力的方案數

哇,正解出來了!!!!!!!!!!

我要ac!!!!

10分!!!!!

不要著急,我們發現這個題目每一條邊不一定是1啊。。

那麼我們應該怎麼辦呢????

假設i到j的體力為9,那麼是不是隻有k等9的時候,f[ i ][ j ]才能變成1;

那麼前8次怎麼辦呢???

我們發現,沒一條邊的邊權從0到9一共10中情況,那麼我們可以吧1個點分成10個點,組成一條邊權為1的鏈,

假設i到j是9,那麼我們可以讓i9與j1相連,

因為,i1與i2為1,i2與i3是1,所以這樣的話,k次方之後i1和j1就可以變成1了,

這道題目也就可以迎刃而解決了

最後f[ 1 ][ n*10-9 ]就是最後的答案!!!

程式碼

 1 #include<bits/stdc++.h>
 2 #define jb cout<<"jb"<<" ";
 3 using namespace std;
 4 inline int read()
 5 {
 6     int x=0,f=1;
 7     char ch=getchar();
 8     while(ch<'0'||ch>'9')
 9     {
10         if(ch=='-')
11         f=-1;
12         ch=getchar();
13     }
14     while(ch>='0'&&ch<='9')
15     {
16         x=(x<<1)+(x<<3)+(ch^48);
17         ch=getchar();
18     }
19     return x*f;
20 }
21 const int m=2009;
22 struct matrix
23 {
24     int a[201][201];
25     void clear()
26     {
27         memset(a,0,sizeof(a));//不想再手寫memset了.....
28     }
29 }v;
30 int n,t,maxn;
31 inline matrix cheng(matrix a,matrix b)
32 {
33     matrix c;
34     c.clear();
35     for(int i=1;i<=n;i++)
36     {
37         for(int k=1;k<=n;k++)
38         {
39             for(int j=1;j<=n;j++)
40             {
41                 c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%m)%m;
42             }
43         }
44     }
45     return c;
46 }
47 inline matrix qpow(matrix a,int p)
48 {
49     matrix b;
50     b.clear();
51     for(int i=1;i<=n;i++)
52     {
53         b.a[i][i]=1;
54     }
55     while(p)
56     {
57         if(p&1)
58         {
59             b=cheng(b,a);
60         }
61         a=cheng(a,a);
62         p>>=1;
63     }
64     return b;
65 }
66 inline int gd(int x,int y)
67 {
68     return (x-1)*10+y;
69 }
70 inline void init()
71 {
72     for(int i=1;i<=n;i++)
73     {
74         for(int j=1;j<=9;j++)
75         {
76             v.a[gd(i,j)][gd(i,j+1)]=1;
77         }
78         int c;
79         for(int j=1;j<=n;j++)
80         {
81             scanf("%1d",&c);//每次只吃掉1個數字,不寫字串型別的了
82             if(c)
83             {
84                 v.a[gd(i,c)][gd(j,1)]=1;
85             }
86         }
87     }
88 }
89 int main()
90 {
91     n=read(),t=read();
92     init();
93     int N=n;
94     n*=10;
95     v=qpow(v,t);
96     printf("%d",v.a[1][10*N-9]);
97 }
3