这道题很巧妙啊.
有两个性质:
1.一个图的最小生成树的每种边权数量是相等的.
2.有 1 得,如果任意一个最小生成树中边权为 $v$ 的边都断掉,$(x,y)$ 连通性在任意 MST 中都相等.
所以我们的做法就是先求出最小生成树,然后分别将每种边权 $v_{i}$ 从最小生成树中都断掉,得到若干联通块,将联通块看作点,再将所有边权为 $v_{i}$ 的重新连回去,跑一个矩阵树定理即可.
code:
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <algorithm>
#define N 103
#define mod 31011
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct UFS
{
int p[N];
void clr() { for(int i=0;i<N;++i) p[i]=i; }
int find(int x) { return p[x]==x?x:p[x]=find(p[x]); }
int merge(int x,int y)
{
x=find(x),y=find(y);
if(x==y) return 0;
p[x]=y;
return 1;
}
}con;
int n,m,cnt,col;
int a[N][N],mark[N*N],val[N],id[N];
struct Edge
{
int x,y,w;
Edge(int x=0,int y=0,int w=0):x(x),y(y),w(w){}
bool operator<(Edge b) const { return w<b.w; }
}tr[N*N];
int gauss()
{
int i,j,k,ans=1;
for(i=1;i<cnt;++i)
{
for(j=i+1;j<cnt;++j)
{
while(a[j][i])
{
int t=a[i][i]/a[j][i];
for(k=i;k<cnt;++k) a[i][k]=(ll)(a[i][k]%mod-(ll)t*a[j][k]%mod+mod)%mod;
swap(a[i],a[j]),ans*=-1;
}
}
if(!a[i][i]) return 0;
}
if(ans<0) ans+=mod;
for(i=1;i<cnt;++i) ans=(ll)((ll)ans*a[i][i]%mod+mod)%mod;
return ans;
}
int kruskal()
{
sort(tr+1,tr+1+m);
int i,j,siz=0;
con.clr();
for(i=1;i<=m;++i)
{
if(con.merge(tr[i].x,tr[i].y))
{
++siz;
mark[i]=1;
if(val[col]!=tr[i].w) val[++col]=tr[i].w;
}
}
return siz==n-1;
}
void link_edge(int v)
{
cnt=0;
con.clr();
int i,j;
for(i=1;i<=m;++i) if(mark[i]&&tr[i].w!=v) con.merge(tr[i].x,tr[i].y);
for(i=1;i<=n;++i) if(con.find(i)==i) id[i]=++cnt;
for(i=1;i<=n;++i) id[i]=id[con.find(i)];
for(i=1;i<=m;++i) if(tr[i].w==v)
{
int x=tr[i].x,y=tr[i].y;
if(id[x]==id[y]) continue;
x=id[x],y=id[y];
++a[x][x],++a[y][y];
--a[x][y],--a[y][x];
}
}
int main()
{
// setIO("input");
int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i) scanf("%d%d%d",&tr[i].x,&tr[i].y,&tr[i].w);
if(!kruskal()) { printf("0\n"); return 0; }
int ans=1;
for(i=1;i<=col;++i)
{
memset(a,0,sizeof(a));
link_edge(val[i]);
ans=(ll)ans*gauss()%mod;
}
printf("%d\n",ans);
return 0;
}