1. 程式人生 > >BZOJ.1016.[JSOI2008]最小生成樹計數(Matrix Tree定理 Kruskal)

BZOJ.1016.[JSOI2008]最小生成樹計數(Matrix Tree定理 Kruskal)

main mat 計算 def tdi str 題目 matrix include

題目鏈接

最小生成樹有兩個性質:
1.在不同的MST中某種權值的邊出現的次數是一定的。
2.在不同的MST中,連接完某種權值的邊後,形成的連通塊的狀態是一樣的。

\(Solution1\)

由這兩個性質,可以先求一個MST,再枚舉每一組邊(權值相同的看做一組邊),對每組邊DFS(\(O(2^{10})\)),若某種方案連通性同MST相同(記錄連通塊個數即可)。則sum++。
最後根據乘法原理,最後的答案即為所有sum相乘。

\(Solution2\)

容易想到MatrixTree定理。
按邊權從小到大處理每一組邊,在加入這組邊之前,之前的邊會構成一些連通塊,而這組邊會一定會將某些連通塊連在一起,如下圖(我也不知道這圖到底是哪的了)


技術分享圖片
把之前形成的每個連通塊看做一個點,這樣又成了一個生成樹計數,生成樹個數即為該種權值的邊的方案數。如下圖:
技術分享圖片
根據乘法原理,我們只要計算出每組邊的這個方案,再乘起來就行了。

//920kb 68ms
#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define mod (31011)
const int N=102,M=1002;

int n,m,A[N][N],tmp[N][N],fa[N],bel[N],Ans;
bool
vis[N]; std::vector<int> v[N]; struct Edge{ int fr,to,val; bool operator <(const Edge &a)const{ return val<a.val; } }e[M]; inline int read() { int now=0;register char c=gc(); for(;!isdigit(c);c=gc()); for(;isdigit(c);now=now*10+c-'0',c=gc()); return
now; } int Get_fa(int x,int *f){//兩個並查集,一個維護MST中的連通性,一個維護所屬連通塊。 return x==f[x]?x:f[x]=Get_fa(f[x],f); } void Gauss(int n) { for(int i=1; i<n; ++i) for(int j=1; j<n; ++j) (A[i][j]+=mod)%=mod;//! bool f=0; for(int j=1; j<n; ++j) { for(int i=j+1; i<n; ++i) while(A[i][j]) { int t=A[j][j]/A[i][j]; for(int k=j; k<n; ++k) A[j][k]=(A[j][k]-t*A[i][k]%mod+mod)%mod; for(int k=j; k<n; ++k) std::swap(A[i][k],A[j][k]); f^=1; } if(!A[j][j]) {Ans=0; break;} Ans=Ans*A[j][j]%mod; } if(f) Ans=mod-Ans;//! } void Calc() { for(int i=1; i<=n; ++i) if(vis[i]) v[Get_fa(i,bel)].push_back(i),vis[i]=0;//處理出每個連通塊所含的點(原先連通塊的代表元素)。 for(int x=1; x<=n; ++x) if(v[x].size()>1) { memset(A,0,sizeof A); for(int i=0,lim=v[x].size(); i<lim; ++i) for(int a=v[x][i],b,j=i+1; j<lim; ++j) { b=v[x][j]; if(tmp[a][b]){//tmp[][]作為邊矩陣可以不清空,因為這倆連通塊不會再同時出現了。 A[i][j]=A[j][i]=-tmp[a][b]; A[i][i]+=tmp[a][b], A[j][j]+=tmp[a][b]; } } Gauss(v[x].size()); } for(int i=1; i<=n; ++i) v[i].clear(), bel[i]=fa[i]=Get_fa(i,bel);//計算完某種邊後把同一連通塊的縮起來。 } int main() { n=read(),m=read(); for(int i=1; i<=m; ++i) e[i].fr=read(),e[i].to=read(),e[i].val=read(); std::sort(e+1,e+1+m); for(int i=1; i<=n; ++i) fa[i]=bel[i]=i; e[0].val=e[1].val, Ans=1; for(int r1,r2,i=1; i<=m; ++i) { if(e[i].val!=e[i-1].val) Calc(); r1=Get_fa(e[i].fr,fa), r2=Get_fa(e[i].to,fa); if(r1==r2) continue; // fa[r1]=r2;//暫時先不連接。 vis[r1]=vis[r2]=1; ++tmp[r1][r2], ++tmp[r2][r1];//, ++tmp[r1][r1], ++tmp[r2][r2];//點的度數矩陣可以之後根據邊處理,tmp[][]用來做邊矩陣。最好這樣,可以不清空。 bel[Get_fa(e[i].fr,bel)]=Get_fa(e[i].to,bel);//統計出每個連通塊。 } Calc();//the last edge for(int i=1; i<n; ++i) if(bel[i]!=bel[i+1]) {Ans=0; break;} printf("%d",Ans); return 0; }

BZOJ.1016.[JSOI2008]最小生成樹計數(Matrix Tree定理 Kruskal)