首页 > Web开发 > 详细

Luogu P4208 [JSOI2008]最小生成树计数

时间:2020-09-04 23:48:30      阅读:112      评论:0      收藏:0      [点我收藏+]

题意

给定一个 \(n\) 个点 \(m\) 条边的图,求最小生成树的个数。

\(\texttt{Data Range:}1\leq n\leq 100,1\leq m\leq 10^4\)

题解

一道好题。

根据本题后面提供的与那题正解没什么关联的方法可知,这个操作过程是这样的:

首先求出原图的某一个最小生成树,接下来考虑从小到大枚举最小生成树上边的边权 \(w\)

将最小生成树上边权不为 \(w\) 的边保留下来进行缩点,接下来再连上不在最小生成树中边权为 \(w\) 的边。

这个时候会建出一个无向图,对每一个可能的 \(w\) 建出的图求一下生成树的个数乘起来即可。

证明的话可以利用 Kruskal 的性质,求生成树的时候使用 Matrix-Tree 定理即可。

模合数求行列式的方法是辗转相消,每一次需要交换两行,行列式要乘上 \(-1\),实在不理解可以看我代码

容易看出这个东西的复杂度是 \(O(n^3\log n)\) 的。

代码

#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=251,MOD=31011;
struct EdgeForKruskal{
    ll from,to,dist;
    inline bool operator <(const EdgeForKruskal &rhs)const
    {
        return this->dist<rhs.dist;
    }
};
EdgeForKruskal ed[2051],tree[MAXN];
ll n,m,x,y,z,kk,res=1,totw; 
ll ffa[MAXN],bel[MAXN],mat[MAXN][MAXN],wt[MAXN];
inline ll read()
{
    register ll num=0,neg=1;
    register char ch=getchar();
    while(!isdigit(ch)&&ch!=‘-‘)
    {
        ch=getchar();
    }
    if(ch==‘-‘)
    {
        neg=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        num=(num<<3)+(num<<1)+(ch-‘0‘);
        ch=getchar();
    }
    return num*neg;
}
inline void add(ll x,ll y)
{
    mat[x][y]--,mat[y][x]--,mat[x][x]++,mat[y][y]++;
}
inline ll find(ll x)
{
    return x==ffa[x]?x:ffa[x]=find(ffa[x]);
}
inline void setup(ll n)
{
    for(register int i=1;i<=n;i++)
    {
        ffa[i]=i;
    }
}
inline void merge(ll x,ll y)
{
    ll fx=find(x),fy=find(y);
    fx!=fy?ffa[fy]=fx:1;
}
inline ll Kruskal()
{
    ll tott=0;
    for(register int i=1;i<=m;i++)
    {
        if(find(ed[i].from)!=find(ed[i].to))
        {
            merge(ed[i].from,ed[i].to),tree[++tott]=ed[i];
            if(wt[totw]!=ed[i].dist)
            {
                wt[++totw]=ed[i].dist;
            }
            if(tott==n-1)
            {
                break;
            }
        }
    }
    return tott==n-1;
}
inline void mergePoint(ll wt)
{
    for(register int i=1;i<n;i++)
    {
        tree[i].dist!=wt?merge(tree[i].from,tree[i].to):(void)1;
    }
}
inline ll det(ll n)
{
    ll res=1,sgn=1,cof;
    for(register int i=1;i<=n;i++)
    {
        for(register int j=1;j<=n;j++)
        {
            mat[i][j]=(mat[i][j]+MOD)%MOD;
        }
    }
    for(register int i=1;i<=n;i++)
    {
        for(register int j=i+1;j<=n;j++)
        {
            while(mat[j][i])
            {
                cof=mat[i][i]/mat[j][i];
                for(register int k=i;k<=n;k++)
                {
                    mat[i][k]=(mat[i][k]-(li)cof*mat[j][k]%MOD+MOD)%MOD;
                }
                swap(mat[i],mat[j]),sgn*=-1;
            }
        }
    }
    for(register int i=1;i<=n;i++)
    {
        res=(li)res*mat[i][i]%MOD;
    }
    return sgn==1?res:MOD-res;
}
inline ll calc(ll wt)
{
    ll blk=0;
    memset(mat,0,sizeof(mat)),setup(n),mergePoint(wt);
    for(register int i=1;i<=n;i++)
    {
        find(i)==i?bel[i]=++blk:1;
    }
    for(register int i=1;i<=n;i++)
    {
        bel[i]=bel[find(i)];
    }
    for(register int i=1;i<=m;i++)
    {
        ed[i].dist==wt?add(bel[ed[i].from],bel[ed[i].to]):(void)1;
    }
    return det(blk-1);
}
int main()
{
    n=read(),m=read();
    for(register int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read(),ed[i]=(EdgeForKruskal){x,y,z};    
    }
    sort(ed+1,ed+m),setup(n);
    if(!Kruskal())
    {
        return puts("0"),0;
    }
    for(register int i=1;i<=totw;i++)
    {
        res=res*calc(wt[i])%MOD;
    }
    printf("%d\n",res);
}

Luogu P4208 [JSOI2008]最小生成树计数

原文:https://www.cnblogs.com/Karry5307/p/13616710.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!