2018.09.22【JSOI2008】【BZOJ1016】最小生成樹計數(矩陣樹定理)(並查集)
阿新 • • 發佈:2018-12-11
解析:
好的這是一道需要數學推理的矩陣樹題目。
首先我們考慮一個問題。
前置定理
我們先隨便做一棵最小生成樹。 重要定理:那麼在這棵生成樹中如果權值為的邊有條,那麼在所有最小生成樹中,權值為的邊都有條。 證明如下: 考慮在這棵生成樹中斷掉一條權值為的邊,使其分為兩個聯通分量。再次連一條新邊,生成一個與原樹不同的最小生成樹。 如果這兩個聯通分量中間只有這一條邊,那就沒有考慮的必要了,它作為橋必然出現在生成樹上。
如果有其他邊?
我們隨便選取一條邊連線著兩個聯通分量。
令新邊權值為考慮如下幾種情況: ,顯然,新的生成樹比原來的小,與原樹是最小生成樹矛盾。 ,顯然,新的生成樹比原來的大,那麼新樹就不是最小生成樹。 所以,要再次生成最小生成樹,只能令。即權值為的邊數量仍然為,歸納一下,原命題得證。
解題思路:
我們把邊權相同的邊一起考慮。
顯然,這些邊會使得一些頂點連線在一起,形成幾個聯通分量。 然後我們將這些聯通分量縮點。我們用下一個權值繼續在縮點後的圖上做連線。繼續縮點。
根據乘法原理,每個聯通分量(以邊權相同,分階段考慮)的生成樹個數乘積就是總生成樹個數。
縮點用並查集實現。
程式碼(巨大常數警告,其實也不是很大 ):
#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;
}