BZOJ 1016: [JSOI2008]最小生成树计数 矩阵树定理+kruskal
这道题很巧妙啊.
有两个性质:
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; }