P4208 [JSOI2008]最小生成树计数

P4208 [JSOI2008]最小生成树计数

矩阵树定理+最小生成树

神犇的题解

↑↑需要的2个定理

根据定理,我们需要求出的是每层相同权值生成树方案之积

所以在最小生成树求解过程中嵌入计算过程:每次建一个新图,计算新图行列式的值。

因为模数不是质数所以高斯消元就用辗转相除了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
template <typename T> inline void read(T &x){
    char c=getchar(); x=0;
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
const int mod=31011;
struct edge{int from,to,dis;}a[1002],c[1002];
inline bool cmp(const edge &A,const edge &B) {return A.dis<B.dis;}
int n,m,ans=1,k,fa1[102],fa2[102],f[102][102],b[102]; bool vis[102];
inline int find1(int x){return fa1[x]==x ? x:fa1[x]=find1(fa1[x]);}
inline int find2(int x){return fa2[x]==x ? x:fa2[x]=find2(fa2[x]);}
inline int det(int t){
    int res=1;
    for(int i=1;i<t;++i){
        for(int j=i+1;j<t;++j){
            while(f[j][i]){ //辗转相除
                int div=f[i][i]/f[j][i];
                for(int k=i;k<t;++k) f[i][k]=(f[i][k]-1LL*f[j][k]*div%mod+mod)%mod;
                swap(f[i],f[j]); res=-res;
            }
        }
        res=(res+mod)%mod*f[i][i]%mod;
    }return (res+mod)%mod;
}
inline void mt(int st,int ed){
    memset(vis,0,sizeof(vis));
    memset(f,0,sizeof(f));
    for(int i=st;i<=ed;++i){
        c[i]=((edge){find1(a[i].from),find1(a[i].to),a[i].dis}); //新边,编号为原连通块的编号
        if(c[i].from==c[i].to) continue;
        if(!vis[c[i].from]) vis[c[i].from]=1;
        if(!vis[c[i].to]) vis[c[i].to]=1;
    }int cnt=0;
    for(int i=1;i<=n;++i) if(vis[i]) b[++cnt]=i,fa2[cnt]=cnt; //新点
    for(int i=st;i<=ed;++i){
        if(c[i].from==c[i].to) continue;
        int r1=find1(c[i].from),r2=find1(c[i].to);
        if(r1!=r2) fa1[r1]=r2,++k;
        int u=lower_bound(b+1,b+cnt+1,c[i].from)-b;
        int v=lower_bound(b+1,b+cnt+1,c[i].to)-b;
        ++f[u][u];++f[v][v];
        f[u][v]=(f[u][v]-1+mod)%mod; //每次减法取模
        f[v][u]=(f[v][u]-1+mod)%mod;
        r1=find2(u),r2=find2(v);
        if(r1!=r2) fa2[r1]=r2;
    }
    for(int i=1;i<cnt;++i){ //为了防止新图不连通,在每个连通块间加入一条唯一任意边。这并不会影响生成树方案数
        int r1=find2(i),r2=find2(i+1);
        if(r1==r2) continue;
        fa2[r1]=r2; ++f[r1][r1];++f[r2][r2];
        f[r1][r2]=(f[r1][r2]-1+mod)%mod;
        f[r2][r1]=(f[r2][r1]-1+mod)%mod;
    }ans=1LL*ans*det(cnt)%mod;
}
int main(){
    read(n); read(m);
    for(int i=1;i<=n;++i) fa1[i]=i;
    for(int i=1;i<=m;++i) read(a[i].from),read(a[i].to),read(a[i].dis);
    sort(a+1,a+m+1,cmp);
    for(int i=1,j;i<=m&&k<n-1;i=j){
        for(j=i+1;j<=m;++j) if(a[i].dis!=a[j].dis) break;
        if(j-i>1) {mt(i,j-1); continue;} //相同权值的边超过1条就要计算生成树的方案了
        int r1=find1(a[i].from),r2=find1(a[i].to);
        if(r1!=r2) fa1[r1]=r2,++k;
    }if(k<n-1) ans=0;
    printf("%d",ans);
    return 0;
}

 

posted @ 2018-09-18 20:34  kafuuchino  阅读(243)  评论(0编辑  收藏  举报