1. 程式人生 > >2018.09.22【JSOI2008】【BZOJ1016】最小生成樹計數(矩陣樹定理)(並查集)

2018.09.22【JSOI2008】【BZOJ1016】最小生成樹計數(矩陣樹定理)(並查集)

傳送門

解析:

好的這是一道需要數學推理的矩陣樹題目。

首先我們考慮一個問題。

前置定理

我們先隨便做一棵最小生成樹。 重要定理:那麼在這棵生成樹中如果權值為ww的邊有tt條,那麼在所有最小生成樹中,權值為ww的邊都有kk條。 證明如下: 考慮在這棵生成樹中斷掉一條權值為ww的邊,使其分為兩個聯通分量。再次連一條新邊,生成一個與原樹不同的最小生成樹。 如果這兩個聯通分量中間只有這一條邊,那就沒有考慮的必要了,它作為橋必然出現在生成樹上。

如果有其他邊?

我們隨便選取一條邊連線著兩個聯通分量。

令新邊權值為ww'考慮如下幾種情況: 1.w<w

1.w'<w,顯然,新的生成樹比原來的小,與原樹是最小生成樹矛盾。 2.w<w2.w'<w,顯然,新的生成樹比原來的大,那麼新樹就不是最小生成樹。 所以,要再次生成最小生成樹,只能令w==ww'==w。即權值為ww的邊數量仍然為tt,歸納一下,原命題得證。

解題思路:

我們把邊權相同的邊一起考慮。

顯然,這些邊會使得一些頂點連線在一起,形成幾個聯通分量。 然後我們將這些聯通分量縮點。我們用下一個權值繼續在縮點後的圖上做連線。繼續縮點。

根據乘法原理,每個聯通分量(以邊權相同,分階段考慮)的生成樹個數乘積就是總生成樹個數。

縮點用並查集實現。

程式碼(巨大常數警告,其實也不是很大 ):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
#define int ll 

cs int mod=31011;

inline
int getint(){
	re int num=0;
	re char c;
	while(!isdigit(c=gc()));
	while(isdigit
(c))num=(num<<1)+(num<<3)+(c^48),c=gc(); return num; } struct matrix{ int arr[101][101]; void reset(){memset(arr,0,sizeof arr);} int det(int n){ int res=1; for(int re i=1;i<=n;++i){ for(int re j=i+1;j<=n;++j){ while(arr[j][i]){ int tmp=arr[i][i]/arr[j][i]; for(int re k=i;k<=n;++k)arr[i][k]=(arr[i][k]-arr[j][k]*tmp)%mod; for(int re k=i;k<=n;++k)swap(arr[i][k],arr[j][k]); res=-res; } } res=(res*arr[i][i])%mod; } return res; } int matrix_tree(int n){ return det(n-1); } int *const operator[](cs int &offset){ return arr[offset]; } }C; int n,tot,cnt,m,ans=1; bool mark[101]; int q[101],pos[101]; int D[101][101]; bool vis[101]; int fa[101]; inline int getfa(int x){ return x==fa[x]?x:fa[x]=getfa(fa[x]); } inline void merge(int i,int j){ i=getfa(i),j=getfa(j); fa[i]=j; } inline void init(){ for(int re i=1;i<=n;++i)fa[i]=i; } struct edge{ int u,v,w; friend bool operator<(cs edge &a,cs edge &b){ return a.w<b.w; } }e[10002]; signed main(){ n=getint(); init(); m=getint(); for(int re i=1;i<=m;++i){ e[i].u=getint(); e[i].v=getint(); e[i].w=getint(); merge(e[i].u,e[i].v); } {//這種寫法能夠令C成為區域性變數,在一些卡空間的題目裡面有奇效,這裡只是個人習慣。 int c=0; for(int re i=1;i<=n;++i)if(fa[i]==i)++c; if(c>1){ puts("0"); return 0; } } init(); sort(e+1,e+m+1); for(int re i=1,j=1;i<=m;i=++j){ while(e[j+1].w==e[i].w&&j+1<=m)++j; memset(D,0,sizeof D); tot=cnt=0; memset(mark,0,sizeof mark); memset(vis,0,sizeof vis); C.reset(); for(int re k=i;k<=j;++k){ int u=getfa(e[k].u); int v=getfa(e[k].v); if(u!=v){ if(!mark[u])mark[u]=true,q[++tot]=u; if(!mark[v])mark[v]=true,q[++tot]=v; ++D[u][u],++D[v][v]; --D[v][u],--D[u][v]; } } for(int re k=i;k<=j;++k)merge(e[k].u,e[k].v); for(int re k=1;k<=tot;++k){ if(!vis[k]){ vis[k]=true,pos[cnt=1]=q[k]; for(int re l=k+1;l<=tot;++l){ if(getfa(q[k])==getfa(q[l])){ pos[++cnt]=q[l]; vis[l]=true; } } for(int re p=1;p<=cnt;++p){ for(int re s=1;s<=cnt;++s){ C[p][s]=D[pos[p]][pos[s]]; } } ans=(ans*C.matrix_tree(cnt))%mod; } } } cout<<(ans+mod)%mod; return 0; }