Bientôt исчезать の happiness.
显然,不同的$MST$,每种边权的出现次数是相同的。而把某个边权从不同的$MST$中删去,得到的联通块形态一样的。
对后者的简单证明:任求一棵$MST$,删去某个边权$x$。对剩下的联通块,假设存在一条长为$y$的边能连接两个联通块。若$y< x$,存在更优的$MST$,不符合当前树是$MST$;若$y>x$,连起来得到的比当前树劣,不可能连这条边。
于是就可以随便搞个$MST$,枚举边权,把$MST$中不是该边权的边连起来,对得到的联通块们和原图中所有等于该边权的边跑矩阵树定理,根据乘法原理乘起来就是答案。
分析一波复杂度是$O(n^3\log n)$的。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define maxn 105
#define inf 0x3f3f3f3f
const int mod = 31011;
using namespace std;
inline int read(){
int x=0,y=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return y?-x:x;
}
int a[maxn][maxn],fa[maxn],id[maxn],cnt,n,m;
struct edge{
int x,y,l;
bool operator < (const edge &a)const{return l<a.l;}
}e[maxn*10],u[maxn];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void add(int x,int y){++a[x][x],++a[y][y],--a[x][y],--a[y][x];}
void kru(){
sort(e+1,e+1+m);
for(register int i=1;i<=n;++i)fa[i]=i;
for(register int i=1;i<=m;++i){
int x=find(e[i].x),y=find(e[i].y);
if(x==y)continue;
u[++cnt]=e[i],fa[x]=y;
}
}
int Gauss(){
int ans=1;
for(register int i=2;i<=cnt;++i){
for(register int j=i+1;j<=cnt;++j)
while(a[j][i]){
int t=a[i][i]/a[j][i];
for(register int k=i;k<=cnt;++k)(a[i][k]-=a[j][k]*t)%=mod,swap(a[i][k],a[j][k]);
ans=-ans;
}
ans=1ll*ans*a[i][i]%mod;
}
return ans;
}
int calc(int l){
cnt=0,memset(a,0,sizeof a);
for(register int i=1;i<=n;++i)fa[i]=i,id[i]=0;
for(register int i=1;i<n;++i)if(u[i].l!=l)fa[find(u[i].x)]=find(u[i].y);
for(register int i=1;i<=n;++i){
int x=find(i);
if(!id[x])id[x]=++cnt;
id[i]=id[x];
}
for(register int i=1;i<=m;++i)if(e[i].l==l&&id[e[i].x]!=id[e[i].y])add(id[e[i].x],id[e[i].y]);
return Gauss();
}
int main(){
n=read(),m=read();
int ans=1;
for(register int i=1;i<=m;++i)e[i].x=read(),e[i].y=read(),e[i].l=read();
kru();
for(register int i=1;i<n;++i)if(u[i].l!=u[i-1].l)ans=1ll*ans*calc(u[i].l)%mod;
printf("%d\n",ans);
}