解析:
好的这是一道需要数学推理的矩阵树题目。
是的,我们还会用到并查集,就像 K r u s k a l Kruskal Kruskal一样。
首先我们考虑一个问题。
前置定理
我们先随便做一棵最小生成树。
重要定理:那么在这棵生成树中如果权值为
w
w
w的边有
k
k
k条,那么在所有最小生成树中,权值为
w
w
w的边都有
k
k
k条。
证明如下:
考虑在这棵生成树中断掉一条权值为
w
w
w的边,使其分为两个联通分量。再次连一条新边,生成一个与原树不同的最小生成树。
如果这两个联通分量中间只有这一条边,那就没有考虑的必要了,它作为桥必然出现在生成树上。
如果有其他边?
我们随便选取一条边连接着两个联通分量。
令新边权值为
w
′
w'
w′考虑如下几种情况:
1.
w
′
<
w
1.w'<w
1.w′<w,显然,新的生成树比原来的小,与原树是最小生成树矛盾。
2.
w
<
w
′
2.w<w'
2.w<w′,显然,新的生成树比原来的大,那么新树就不是最小生成树。
所以,要再次生成最小生成树,只能令
w
′
=
=
w
w'==w
w′==w。即权值为
w
w
w的边数量仍然为
t
t
t,归纳一下,原命题得证。
解题思路:
我们把边权相同的边一起考虑。
显然,这些边会使得一些顶点连接在一起,形成几个联通分量。
然后我们将这些联通分量缩点。我们用下一个权值继续在缩点后的图上做连接。继续缩点。
根据乘法原理,每个联通分量(以边权相同,分阶段考虑)的生成树个数乘积就是总生成树个数。
缩点用并查集实现。
代码(巨大常数警告,其实也不是很大 ):
#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;
}