线性代数初步
1
矩阵乘法规则:
\(\begin{bmatrix}a_{1,1}&a_{1,2}&a_{1,3}\\a_{2,1}&a_{2,2}&a_{2,3}\end{bmatrix} \times \begin{bmatrix}b_{1,1}&b_{1,2}\\b_{2,1}&b_{2,2}\\b_{3,1}&b_{3,2}\end{bmatrix}=\begin{bmatrix}a_{1,1}b_{1,1}+a_{1,2}b_{2,1}+a_{1,3}b_{3,1}&a_{1,1}b_{1,2}+a_{1,2}b_{2,2}+a_{1,3}b_{3,2}\\a_{2,1}b_{1,1}+a_{2,2}b_{2,1}+a_{2,3}b_{3,1}&a_{2,1}b_{1,2}+a_{2,2}b_{2,2}+a_{2,3}b_{3,2}\end{bmatrix}\).
以此类推,若 \(A \times B = C\) ,则 \(C\) 第 \(m\) 行第 \(n\) 列的元素等于 \(A\) 第 \(m\) 行的元素与 \(B\) 第 \(n\) 列的元素对应乘积之和。
struct mat
{
int r,c;
int A[N][N];
mat()
{
memset(A,0,sizeof(A));
}
};
mat operator * (mat x,mat y)
{
mat ans;
ans.r=x.r,ans.c=y.c;
for(int i=1;i<=ans.r;i++)
for(int j=1;j<=ans.c;j++)
for(int k=1;k<=x.c;k++)
ans.A[i][j]+=x.A[i][k]*y.A[k][j]%p,ans.A[i][j]%=p;
return ans;
}
若要算连续的矩阵乘法,可以用快速幂优化,写法与普通快速幂类似。
根据矩阵乘法的计算方式,它可以用来处理某些类型的 dp 。
尤其是当状态数较少但转移次数较多,且转移相同时,用矩阵快速幂优化可以达到很好的复杂度。
2
#include<iostream>
#include<cstdlib>
#include<cstdio>
#define db double
using namespace std;
const int N=110;
const db eps=1e-9;
int n;
db a[N][N],ans[N];
db ABS(db x)
{
return x>eps?x:-x;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++) scanf("%lf",&a[i][j]);
for(int i=1;i<=n;i++)
{
int t=i;
for(int j=i+1;j<=n;j++)
if(ABS(a[j][i])-ABS(a[t][i])>eps) t=j;
swap(a[t],a[i]);
//找到第i个元素最大的行,换上来(减小精度误差)
if(ABS(a[i][i])<eps)
puts("No Solution"),exit(0);
for(int j=i+1;j<=n+1;j++) a[i][j]/=a[i][i];
a[i][i]=1;
//把系数消成1,方便后面的操作
for(int j=i+1;j<=n;j++)
{
for(int k=i+1;k<=n+1;k++)
a[j][k]-=a[i][k]*a[j][i];
a[j][i]=0;
}
}
for(int i=n;i>=1;i--)
{
ans[i]=a[i][n+1];
for(int j=i+1;j<=n;j++) ans[i]-=a[i][j]*ans[j];
}
for(int i=1;i<=n;i++) printf("%.2lf\n",ans[i]);
return 0;
}
3
求行列式
与高斯消元类似,但是不能直接使用实数(避免误差)。
要用一行消去另一行时,用辗转相减的思想,每次消去最大的能消的整数倍,再把两行交换,以此类推。
注意,交换时行列式变号。
复杂度仍为 \(O(n^3)\) 。
#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int N=1010;
int n,p;
int a[N][N];
signed main()
{
scanf("%lld%lld",&n,&p);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%lld",&a[i][j]),a[i][j]%=p;
int ans=1;
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
{
while(a[i][i])
{
int t=a[j][i]/a[i][i];
for(int k=i;k<=n;k++)
a[j][k]-=a[i][k]*t%p,a[j][k]+=p,a[j][k]%=p;
swap(a[i],a[j]),ans=-ans;
}
swap(a[i],a[j]),ans=-ans;
}
for(int i=1;i<=n;i++) ans*=a[i][i],ans%=p;
printf("%lld",(ans+p)%p);
return 0;
}
4
矩阵树定理。
1
无向无权图。
设 \(A\) 为邻接矩阵,\(D\) 为度数矩阵(\(D_{i,i}\) 为 \(i\) 点的度数,其它值为 0)。
基尔霍夫矩阵为 \(K=A-D\) 。
令 \(K'\) 为 \(K\) 去掉第 \(k\) 行第 \(k\) 列后得到的矩阵,
则 \(\det(K')\) 即为该图的生成树个数。
(取任意 \(k\) 时得到的结果均相等)
还可以处理有重边的情况。(若 \(i\) 与 \(j\) 之间有 \(x\) 条边,则 \(A_{i,j}=A_{j,i}=x\))
2
有向图。
若根为 \(r\) ,则 \(K'\) 为 \(K\) 去掉第 \(r\) 行第 \(r\) 列后得到的矩阵。
求内向生成树(由外向根)时, \(D_{i,i}=\sum \limits_{j=1}^n A_{i,j}\) .
求外向生成树(由根向外)时, \(D_{i,i}=\sum \limits_{j=1}^n A_{j,i}\) .
有重边时,同样可以处理。
3
有权图。
把边权理解为重边,同样用上面的方法。
此时求的就是所有生成树边权乘积的总和。