标签:开始 line i++ scanf fine 暴力 ++ can 有向图
矩阵树就是指计算一个图生成树的个数(无论有没有环,边带不带权,有向或无向,指不指定根节点,外向树或内向树,都可以计算)
在此之前,我们引入一个问题:
问题1:给定一个n个节点的完全图,求其生成树个数。
这个问题,简单的prufer序列就可以解决,显然,答案是$n^{n-2}$。
一次升级版:
问题2:给定一个n个节点的无边权无向图,求其生成树个数。
怎么解决?还是prufer序列?似乎不太可行。
这时候,计数利器-------线性代数出现了。
什么是线性代数?没学过?不着急,矩阵乘法总学过吧。那就没问题。
我们对于一个矩阵,定义一个值,叫做行列式的值。
这个值的求法如下:
我们有着$n!$个1~n的排列p,
对于一个n阶矩阵:$\sum_{1}^{n!} (-1)^{f(p)}\prod_{j=1}^{n}a_{j,p_j}$这个便是这个n阶矩阵的行列式的值
其中:$f(p)$表示当排列是p的情况下逆序对的个数。
我们要求这个值怎么办?暴力显然不可取。
当暴力不行的时候,我们挖掘值的性质。
交换矩阵的任意两行,行列式变号。
证明:一个排列交换其中任意两个元素,逆序对个数变化量为奇数。(不会的去学学逆序对)。
矩阵的一行都乘以 k ,行列式也乘以 k
证明:根据行列式值得定义,每一行仅仅会取一个元素,所以每一个累加中答案乘k,总答案也就乘k。
如果矩阵内有两行相等,那么行列式为 0
证明:由性质1知:交换两行后,行列式的值$\times -1$,那么在实数范围内仅有$0=-0$,得证。
如果一行是另一行的 k 倍,那么行列式值为0。
证明:由性质2和3易证,如果一行乘k,那么会存在两行相等。即:答案乘k后等于0。在实数范围内,仅有0可能是答案,得证。
一行加上另一行的 k 倍,行列式不变。
证明:我们可以把现矩阵的行列式拆成两个行列式相加,一个是原先矩阵的行列式,另一个是增长的行列式。根据性质 4 ,可以得到增长的行列式为 0 。
看到这里,相比很多人已经能明白了吧,如果我们可以得到一个上三角矩阵,那么正对角线的乘积便是其行列式的值。
那么如何得到这个上三角矩阵呢?显然是通过高斯消元在$n^3$的时间内得到上三角矩阵。
但要注意,和普通的高斯消元不同的是:我们交换某两行,行列式的值$\times -1$。
其余的便没什么大问题了。
但到目前为止对于求生成树个数应该还没什么头绪那吧,那么,我们引来一尊大人物:Kirchhoff矩阵
Kirchhoff 矩阵是指的对于一个图构造出来的一个矩阵。具体定义为度数矩阵减去邻接矩阵。
度数矩阵:$\left\{\begin{matrix}degree &,i==j \\ 0 & ,otherwise \end{matrix}\right.$
至于邻接矩阵便是我们存图时经常用的东西。
Matrix Tree定理
一个图中的生成树个数等于其Kirchhoff 矩阵的任意一个 代数余子式的行列式(即:随意去掉矩阵中的第k行和第k列,注意一定要是同一行和列,然后剩下的矩阵跑高斯消元就好了)。
由于其证明繁杂,这里就不说了,感兴趣的可以了解其他大神的博客。
到目前为止,我们可以完成圆满的完成问题2了。可是在NOI的范围内,这还远远不够,让我们接着看:
问题3:给定一个n个节点的带边权无向图,定义其一个生成树T的权值为T中所有边权的乘积。求其所有生成树的权值之和。
我们定义重边:对于两个点(x,y)之间若有大于1条边直接相连,那么这几条边就都可以叫做重边。
容易理解 : 带重边的情况,上面的经典矩阵树定理也是能够处理的。
根据乘法原理,对于某种生成树的形态,其贡献为每条边重复的次数的乘积。
如果把重复边次数理解成权值的话,那么矩阵树定理求的就是 : 所有生成树边权乘积的总和。
因此我们将度数矩阵变成与相邻边的权值和,邻接矩阵从0,1变成边的权值,然后跑矩阵树即可。
问题3.5:给定一个n个节点的无边权无向图,以k节点为根,求以k节点为根的生成树个数
这个问题其实并不存在,因为无向图的生成树必定是无根树。无论是否指定根节点对答案都无影响。但我将其放在这里的原因是因为与下面的有根树有所对应对应:我们指定了根节点。
我们发现,若以k为根,那么在求基尔霍夫矩阵的代数余子式的行列式值的时候,限定删去第k行和第k列,这样就可以
问题4:给定一个n个节点的带边权有向图,以1为根,求其外向树的个数。
外向树,顾名思义,就是所有边都从1开始走,一直走到叶子节点。
我们把度数矩阵改为:到该点的边权总和。接着求基尔霍夫矩阵的代数余子式的行列式值的时候,限定删去第1行和第1列然后跑矩阵树即可。
问题5:给定一个n个节点的带边权有向图,以1为根,求其内向树的个数。
外向树,顾名思义,就是所有边都从叶子节点开始走,一直走到1号点。
我们把度数矩阵改为:从该点出发的边权总和。接着求基尔霍夫矩阵的代数余子式的行列式值的时候,限定删去第1行和第1列然后跑矩阵树即可。
接下来便是愉悦的代码时间:
#include <bits/stdc++.h> #define inc(i,a,b) for(register int i=a;i<=b;i++) using namespace std; const int p=1e9+7; int mmap[321][321]; long long kir[321][321]; int n,m,t,line,id[321]; long long KSM(long long a,long long b){ long long res=1; while(b){ if(b&1) res=res*a%p; a=a*a%p; b/=2; } return res; } long long ans=1,cnt=0; void GUASS(){ inc(i,2,n){ int maxn=i; inc(j,i +1,n) if(abs(kir[j][i])>abs(kir[maxn][i])) maxn=j; if(!kir[maxn][i]){ ans=0; return; } inc(j,i,n) swap(kir[maxn][j],kir[i][j]); if(maxn!=i) ++cnt; inc(j,2,n){ if(j==i) continue; long long tmp=kir[j][i]*KSM(kir[i][i],p-2)%p; inc(k,i,n) kir[j][k]=(kir[j][k]-kir[i][k]*tmp%p+p)%p; } } } int main(){ //freopen("1.in","r",stdin); //freopen("") cin>>n>>m>>t; inc(i,1,m){ int x,y,z; scanf("%d%d%d",&x,&y,&z); if(t==1){ kir[x][y]=(kir[x][y]-z+p)%p; kir[y][y]=(kir[y][y]+z)%p; } else{ kir[x][y]=(kir[x][y]-z+p)%p; kir[y][y]=(kir[y][y]+z)%p; kir[y][x]=(kir[y][x]-z+p)%p; kir[x][x]=(kir[x][x]+z)%p; } } GUASS(); inc(i,2,n) ans=ans*kir[i][i]%p; if(cnt&1) ans=p-ans; printf("%lld\n",ans%p); }
标签:开始 line i++ scanf fine 暴力 ++ can 有向图
原文地址:https://www.cnblogs.com/kamimxr/p/13121575.html