[bzoj1016][JSOI2008]最小生成树计数

题目大意

给定n个点m条无向边的图G,求G的最小生成树个数,模31011。
其实有个特殊条件但我们的高端做法可以忽略。
n<=100,m<=1000

矩阵树定理

我们来思考一个图G所有最小生成树的同一个性质:
假如我加入所有边权<=w的边,任何生成树的联通情况一定是一致的。
脑补得证QAQ
或者你考虑反证+切割引理。
我们把边权相同的边当做一组边,每次在原来的基础上把这样边都加入。例如原本三个在 <w <script type="math/tex" id="MathJax-Element-107">
但是模数不是质数,让我们消元很头痛,有个精妙的方法。
那就是例如i和j两行,要将第j行消掉,我们可以辗转相消(相当于对(i,i)和(j,i)两项做gcd的过程)!详见代码。
然后这样的复杂度是多少我不太会分析,一个十分松的上界是n^4。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100+10,maxm=1000+10,mo=31011;
struct dong{
    int u,v,l;
} edge[maxm];
int c[maxn][maxn],cr[maxn][maxn],d[maxn][maxn],a[maxn][maxn],dis[maxn][maxn];
int fa[maxn],st[maxn],rj[maxn],bh[maxn],hb[maxn],ds[maxn];
int i,j,k,s,l,r,t,n,m,tot,top,ans,root;
bool czy;
bool cmp(dong a,dong b){
    return a.l<b.l;
}
bool cmp2(int a,int b){
    return rj[a]<rj[b];
}
int getfa(int x){
    return fa[x]?fa[x]=getfa(fa[x]):x;
}
int det(){
    int n=t-1;
    int cnt=1;
    int i,j,k,t;
    fo(i,1,n)
        fo(j,1,n)
            cr[i][j]%=mo;
    fo(i,1,n){
        fo(j,i+1,n)
            while (cr[j][i]){
                t=cr[i][i]/cr[j][i];
                fo(k,i,n)
                    cr[i][k]-=cr[j][k]*t;
                fo(k,i,n) swap(cr[i][k],cr[j][k]);
                cnt=-cnt;
            }
        if (!cr[i][i]) return 0;
        cnt=cnt*cr[i][i]%mo;
    }
    return cnt;
}
int main(){
    scanf("%d%d",&n,&m);
    fo(i,1,m){
        scanf("%d%d%d",&j,&k,&l);
        edge[i].u=j;edge[i].v=k;
        edge[i].l=l;
    }
    sort(edge+1,edge+m+1,cmp);
    l=1;
    ans=1;
    while (l<=m){
        r=l;
        while (r<m&&edge[r+1].l==edge[l].l) r++;
        top=0;
        fo(i,1,n) bh[i]=0;
        fo(i,1,n)
            if (!fa[i]) st[++top]=i,bh[i]=top;
        fo(i,1,top) ds[i]=0;
        fo(i,1,top)
            fo(j,1,top)
                dis[i][j]=0;
        fo(i,l,r){
            j=getfa(edge[i].u);k=getfa(edge[i].v);
            if (j==k) continue;
            ds[bh[j]]++;ds[bh[k]]++;
            dis[bh[j]][bh[k]]++;
            dis[bh[k]][bh[j]]++;
        }
        fo(i,l,r){
            if (getfa(edge[i].u)!=getfa(edge[i].v)){
                fa[getfa(edge[i].u)]=getfa(edge[i].v);
            }
        }
        fo(i,1,top) rj[st[i]]=getfa(st[i]);
        sort(st+1,st+top+1,cmp2);
        i=1;
        while (i<=top){
            j=i;
            while (j<top&&rj[st[j+1]]==rj[st[i]]) j++;
            if (i==j){
                i++;
                continue;
            }
            t=0;
            fo(k,i,j) hb[++t]=bh[st[k]];
            fo(k,1,t)
                fo(s,1,t)
                    if (k!=s) d[k][s]=0;else d[k][s]=ds[hb[k]];
            fo(k,1,t)
                fo(s,1,t)
                    a[k][s]=dis[hb[k]][hb[s]];
            fo(k,1,t)
                fo(s,1,t)
                    c[k][s]=d[k][s]-a[k][s];
            fo(k,1,t-1)
                fo(s,1,t-1)
                    cr[k][s]=c[k+1][s+1];
            ans=ans*det()%mo;
            i=j+1;
        }
        l=r+1;
    }
    czy=1;
    root=0;
    fo(i,1,n)
        if (!fa[i]){
            if (!root) root=i;
            else{
                czy=0;
                break;
            }
        }
    (ans+=mo)%=mo;
    if (czy) printf("%d\n",ans);else printf("0\n");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值