1. 程式人生 > 其它 >一個關於Python的死鎖問題

一個關於Python的死鎖問題

矩陣運算入門,講解了矩陣的加減法,數乘,轉置,乘法運算並作了一定的拓展

矩陣

矩陣定義

矩陣(Matrix)通俗地講可以看做一個二維陣列,每個位置上都是一個數字,更準確地說它是一個按照矩形陣列排列的實數或複數集合。

下面來看看矩陣的運算,其中矩陣加減法和數乘矩陣被稱為矩陣的線性運算

矩陣加減法

定義

矩陣加減法僅在矩陣形態相同時被定義,也就是兩個矩陣行數列數均相同時才有加減法。矩陣的加法和減法就是把它們對應位置上的數字相加減,假設有兩個矩陣 \(A,B\),均有 \(n\)\(m\) 列,那麼 \(A \pm B\) 得到的新矩陣 \(C\) 也是 \(n*m\) 的矩陣,其中 \(\forall i\in[1,n],\forall j\in[1,m],\ C_{i,j}=A_{i,j}\pm B_{i,j}\)

,更形象的寫法如下:

\[A_{n,m}\pm B_{n,m}= \left(\begin{matrix} a_{1,1} & \cdots & a_{1,m}\\ \vdots & \ddots & \vdots\\ a_{n,1} & \cdots & a_{n,m} \end{matrix}\right)\pm \left(\begin{matrix} b_{1,1} & \cdots & b_{1,m}\\ \vdots & \ddots & \vdots\\ b_{n,1} & \cdots & b_{n,m} \end{matrix}\right)= \left(\begin{matrix} a_{1,1}\pm b_{1,1} & \cdots & a_{1,m}\pm b_{1,m}\\ \vdots & \ddots & \vdots\\ a_{n,1}\pm b_{n,1} & \cdots & a_{n,m}\pm b_{n,m} \end{matrix}\right) \]

運算律

矩陣加減法滿足如下運算律(注意 \(A,B,C\) 都必須是同型矩陣):

交換律:\(A+B=B+A\)

結合律:\(A+(B+C)=(A+B)+C\)

數乘矩陣

定義

一個數字乘一個矩陣就是把矩陣中的每一個數都乘上這個數字。假設 \(B=\lambda A\),那麼有 \(\forall i\in[1,n],\forall j\in[1,m],\ B_{i,j}=\lambda A_{i,j}\),即:

\[\lambda A_{n,m}=\lambda\cdot \left(\begin{matrix} a_{1,1} & \cdots & a_{1,m}\\ \vdots & \ddots & \vdots\\ a_{n,1} & \cdots & a_{n,m} \end{matrix}\right)= \left(\begin{matrix} \lambda a_{1,1} & \cdots & \lambda a_{1,m}\\ \vdots & \ddots & \vdots\\ \lambda a_{n,1} & \cdots & \lambda a_{n,m} \end{matrix}\right) \]

運算律

數乘矩陣滿足如下運算律:

交換律:\(\lambda(\mu A)=\mu(\lambda A)=(\lambda\mu)A\)

\((\lambda+\mu)A=\lambda A+\mu A\)

\(\lambda(A+B)=\lambda A+\lambda B\)

矩陣轉置

定義

假設有一個 \(n\)\(m\) 列的矩陣 \(A\),那麼我們把 \(A\) 中的每一行都換成序號相同的列,把每一列都換成序號相同的行,得到的新矩陣就稱作原矩陣 \(A\) 的轉置矩陣記為 \(A^T\)\(A'\),此處 \(A\) 的轉置矩陣就是 \(m\)\(n\) 列的

也就是說 \(\forall i\in[1,n],\forall j\in[1,m],\ A^T_{j,i}=A_{i,j}\)

運算律

矩陣轉置滿足如下運算律:

\((A^T)^T=A\)

\((A^T+B^T)=A^T+B^T\)

\((\lambda A)^T=\lambda(A^T)\)

\((AB)^T=B^TA^T\)

矩陣乘法

定義

僅當一個矩陣的列數與另一個矩陣的行數相同時矩陣乘法才被定義,假設矩陣 \(A\)\(n*m\) 的矩陣,矩陣 \(B\)\(m*p\) 的矩陣,那麼 \(A*B\) 所得到的矩陣 \(C\) 就是一個 \(n*p\) 的矩陣,其中:

\[\forall i\in[1,n],\forall j\in[1,p]\qquad C_{i,j}=\sum_{k=1}^mA_{i,k}*B_{k,j} \]

也就是說參與矩陣乘法運算的兩個矩陣中,第一個矩陣的列數等於第二個矩陣的行數,算出的新矩陣的行數為第一個矩陣的行數,新矩陣的列數為第二個矩陣的列數,新矩陣中第 \(i\) 行第 \(j\) 列的元素就等於第一個矩陣的第 \(i\) 行的所有元素與第二個矩陣的第 \(j\) 行的所有元素分別相乘,然後把乘積相加得到的結果

下面來舉個例子幫助直觀理解,例如我們令 \(AB=C\),那麼我們可以展開寫成:

\[\left(\begin{matrix} a_{1,1} & a_{1,2} & a_{1,3} & a_{1,4}\\ a_{2,1} & a_{2,2} & a_{2,3} & a_{2,4}\\ a_{3,1} & a_{3,2} & a_{3,3} & a_{3,4} \end{matrix}\right)\ast \left(\begin{matrix} b_{1,1} & b_{1,2} & b_{1,3}\\ b_{2,1} & b_{2,2} & b_{2,3}\\ b_{3,1} & b_{3,2} & b_{3,3}\\ b_{4,1} & b_{4,2} & b_{4,3} \end{matrix}\right)= \left(\begin{matrix} c_{1,1} & c_{1,2} & c_{1,3}\\ c_{2,1} & c_{2,2} & c_{2,3}\\ c_{3,1} & c_{3,2} & c_{3,3} \end{matrix}\right) \]

那麼 \(c_{1,2}\)\(c_{2,1}\) 分別是由哪些數字相乘得到的呢?

從圖上就能直觀地看出 \(c_{1,2}=a_{1,1}*b_{1,2}+a_{1,2}*b_{2,2}+a_{1,3}*b_{3,2}+a_{1,4}*b_{4,2}\) ,也就是 \(A\) 的第一行和 \(B\) 的第二列相乘相加

運算律

矩陣乘法滿足如下運算律:

乘法結合律:\((AB)C=A(BC)\)

乘法(左、右)分配律:\((A+B)C=AC+BC\) 以及 \(C(A+B)=CA+CB\)

數乘結合性:\((\lambda A)B=\lambda(AB)=A(\lambda B)\)

c++ code

int a[MAXN][MAXN],b[MAXN][MAXN],n,m,p;
//這裡省略讀入,假設輸入的a,b分別為n行m列和m行p列的矩陣,從下標0開始存放
int c[n][p];//矩陣乘法結果為一個n行p列的矩陣
memset(c,0,sizeof(c));//初始化陣列
for(int i=0;i<n;i++)
    for(int j=0;j<p;j++)
        for(int k=0;k<m;k++)//列舉
        	c[i][j]+=a[i][k]*b[k][j];//乘上並加入c中
//最後c中存放的矩陣即為矩陣乘法的結果

拓展與應用

單位矩陣

單位矩陣在矩陣乘法中有重要作用,它相當於普通數字乘法中的 \(1\),任何矩陣左乘或者右乘單位矩陣都等於這個矩陣自身

單位矩陣是一個方陣(全稱方塊矩陣,指行數列數相等的矩陣),它的左上角到右下角對角線上的所有元素都為 \(1\),其他元素都是 \(0\),記作 \(I_n\)\(E_n\),常用 \(I\)\(E\) 表示

\[I_n=E_n= \left(\begin{matrix} 1 & 0 & \cdots & 0\\ 0 & 1 & \cdots & 0\\ \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & \cdots & 1 \end{matrix}\right) \]

滿足性質:

\[AE_n=E_nA=A \]

矩陣快速冪

對於一個方陣來說,顯然它反覆乘以自己得到的仍舊是相同大小的方陣,如果我們需要反覆乘同一個方陣,單純 \(\Theta(n)\) 求就會導致效率極其低下,在以前相信大家都學過快速冪,由於矩陣乘法的運算律,我們仍然可以使用快速冪計算一個方陣的冪,思路與普通的快速冪極為類似,所以就不多解釋了,不瞭解快速冪可以自行查閱相關資料,

注意這裡 \(ans\) 矩陣一開始應該初始化為單位矩陣

下面直接上程式碼:

const int mod=1e9+7;
int n,p;
cin>>n>>p;
long long a[n][n],ans[n][n],tmp[n][n];
for(int i=0;i<n;i++)//初始化
{
    for(int j=0;j<n;j++)
        cin>>a[i][j],ans[i][j]=tmp[i][j]=0;
    ans[i][i]=1;
}
while(p)//快速冪
{
    if(p&1)
    {
        for(int i=0;i<n;i++)//tmp=ans*a
            for(int j=0;j<n;j++)
                for(int k=0;k<n;k++)
                    tmp[i][j]=(tmp[i][j]+ans[i][k]*a[k][j]%mod)%mod;
        for(int i=0;i<n;i++)//ans=tmp
            for(int j=0;j<n;j++)
                ans[i][j]=tmp[i][j],tmp[i][j]=0;
    }
    for(int i=0;i<n;i++)//tmp=a*a
        for(int j=0;j<n;j++)
            for(int k=0;k<n;k++)
                tmp[i][j]=(tmp[i][j]+a[i][k]*a[k][j]%mod)%mod;
    for(int i=0;i<n;i++)//a=tmp
        for(int j=0;j<n;j++)
            a[i][j]=tmp[i][j],tmp[i][j]=0;
    p>>=1;
}
for(int i=0;i<n;i++)//輸出
{
    for(int j=0;j<n;j++)
        cout<<ans[i][j]<<' ';
    putchar('\n');
}

這樣碼風有點醜,只是為了展示方便,沒有寫成結構體+函式的形式,自己做題時建議都寫成結構體形式,更方便除錯

加速遞推

舉個栗子:點我傳送 Luogu P1962 斐波那契數列

題意簡述:給定 \(n\),請你給出斐波那契數列第 \(n\) 項對 \(10^9+7\) 取模的結果,其中 \(1\le n<2^{63}\)

如果暴力解決這道題那麼就是 \(\Theta(n)\) 遞推,但由於 \(n\) 過大,顯然會超時,利用矩陣就可以優化

我們設一個 \(1*2\) 的矩陣 \(A_i=\left(\begin{matrix}F_i & F_{i-1}\end{matrix}\right)\),其中 \(F_i\) 表示斐波那契數列的第 \(i\) 項,那麼根據斐波那契數列的定義有 \(A_{i+1}=\left(\begin{matrix}F_i+F_{i-1} & F_{i}\end{matrix}\right)\),我們發現可以找到一個矩陣 \(B=\left(\begin{matrix}1 & 1\\1 & 0\end{matrix}\right)\) 使得 \(A_i B=A_{i+1}\) 即:

\[\left(\begin{matrix}F_i & F_{i-1}\end{matrix}\right)*\left(\begin{matrix}1 & 1\\1 & 0\end{matrix}\right)=\left(\begin{matrix}F_i+F_{i-1} & F_{i}\end{matrix}\right) \]

簡單手動計算一下就可以知道這個式子的正確性

所以斐波那契數列的 \(A_n(n\ge3)\) 就可以表示為 \(A_1 B^{n-2}\),根據矩陣乘法的運算律,我們利用矩陣快速冪把 \(B^{n-2}\) 算出來再用 \(A_1\) 乘,就能在 \(\Theta(\log n)\) (常數有點大,主要是矩陣乘法的 \(\Theta(2^3)\))的時間複雜度內解決這個問題

上面這種方法就是利用矩陣乘法加速遞推,類似的線性遞推題目可以嘗試使用矩陣乘法進行優化,上面題目中矩陣 \(A\) 就稱作狀態矩陣,矩陣 \(B\) 就稱作轉移矩陣,正確地定義狀態矩陣並計算出轉移矩陣就可以成功優化


該文為本人原創,轉載請註明出處

部落格園傳送門

洛谷傳送門