图论 —— 生成树 —— 生成树计数

【概述】

给出一个由 n 个点和 m 条边构成的简单无向加权图,有时需要对生成树计数或对最小生成树计数。

当对生成树计数时,利用基尔霍夫矩阵的 Matrix-Tree 定理即可解决,而对最小生成树计数时,根据数据范围的不同,所采用的方法也不同。

关于基尔霍夫矩阵:点击这里

【生成树计数】

对于生成树的计数,一般采用矩阵树定理(Matrix-Tree 定理)来解决。

Matrix-Tree 定理的内容为:对于已经得出的基尔霍夫矩阵,去掉其随意一行一列得出的矩阵的行列式,其绝对值为生成树的个数

因此,对于给定的图 G,若要求其生成树个数,可以先求其基尔霍夫矩阵,然后随意取其任意一个 n-1 阶行列式,然后求出行列式的值,其绝对值就是这个图中生成树的个数

LL K[N][N];
LL gauss(int n){//求矩阵K的n-1阶顺序主子式
    LL res=1;
    for(int i=1;i<=n-1;i++){//枚举主对角线上第i个元素
        for(int j=i+1;j<=n-1;j++){//枚举剩下的行
            while(K[j][i]){//辗转相除
                int t=K[i][i]/K[j][i];
                for(int k=i;k<=n-1;k++)//转为倒三角
                    K[i][k]=(K[i][k]-t*K[j][k]+MOD)%MOD;
                swap(K[i],K[j]);//交换i、j两行
                res=-res;//取负
            }
        }
        res=(res*K[i][i])%MOD;
    }
    return (res+MOD)%MOD;
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    memset(K,0,sizeof(K));
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        K[x][x]++;
        K[y][y]++;
        K[x][y]--;
        K[y][x]--;
    }
    printf("%lld\n",gauss(n));
    return 0;
}

【最小生成树计数】

根据给出的 n、m 的数据范围,最小生成树的计数分为简化版、加强版。

最小生成树的计数的核心为 MST 的以下两条性质:

  • 每种权值的边的数量是固定的
  • 不同的生成树中,某一种权值的边任意加入需要的数量后,形成的联通块状态是相同的

1.简化版

当每种权值的边不超过 10 条时,利用 dfs,暴力枚举即可解决,时间复杂度为 O(2^{10} m)

根据最小生成树的性质,先统计出每种权值需要的边的条数 cnt

然后进行 dfs,枚举第 i 种权值的边选择哪 cnt​ 条,然后根据乘法原理来统计答案

需要注意的是,为了能够快速分开连通块,并查集中不能使用路径压缩

struct Edge {
    int x,y;
    int dis;
    bool operator < (const Edge &rhs)const{
        return dis<rhs.dis;
    }
} edge[N];
struct build {
    int l,r;
    int cnt;
} a[N];
int tot;
int n,m;
int father[N];
int sum;

int Find(int x) {
    if(father[x]!=x)
        return Find(father[x]);
    return father[x];
}
void dfs(int x,int now,int num) {
    if(now==a[x].r+1) {
        if(num==a[x].cnt)//选指定的条数
            sum++;
        return ;
    }
    int fx=Find(edge[now].x);
    int fy=Find(edge[now].y);
    if(fx!=fy) {//选
        father[fx]=fy;
        dfs(x,now+1,num+1);
        father[fx]=fx;
        father[fy]=fy;
    }
    dfs(x,now+1,num);//不选
}
int Kruskal() {
    sort(edge+1,edge+m+1);
    for(int i=1; i<=n; i++)
        father[i]=i;

    int cnt=0;
    for(int i=1; i<=m; i++) {
        if(edge[i].dis!=edge[i-1].dis) {
            tot++;
            a[tot].l=i;
            a[tot-1].r=i-1;
        }
        int x=Find(edge[i].x);
        int y=Find(edge[i].y);
        if(x!=y) {
            father[y]=x;
            a[tot].cnt++;
            cnt++;
        }
    }
    a[tot].r=m;

    return cnt;
}
int main() {
    scanf("%d%d",&n,&m);
    int x,y,z;
    for(int i=1; i<=m; i++)
        scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].dis);

    int num=Kruskal();

    if(num!=n-1) {
        printf("0");
        return 0;
    }
    else{
        for(int i=1; i<=n; i++)
            father[i]=i;

        int res=1;
        for(int i=1; i<=tot; i++) {
            sum=0;

            dfs(i,a[i].l,0);
            res=res*sum;

            for(int j=a[i].l; j<=a[i].r; j++) {
                int x=Find(edge[j].x);
                int y=Find(edge[j].y);
                if(x!=y)
                    father[y]=x;
            }
        }

        printf("%d",res);
    }
    return 0;
}

2.加强版

当每种权值的边不超过 100 条,需要利用 Matrix-Tree 定理,时间复杂度为 O(n^3 logn)

根据 MST 的性质,枚举树边的权值 i,将权值不是 i 的树边都加入到图中然后进行缩点。将权值是 i 的原图中的边,在缩点后构造基尔霍夫矩阵,再利用 Matrix-Tree 定理求出方案数。

最终答案根据乘法原理进行计算即可。

struct Edge {
    int x,y;
    int dis;
    bool operator < (const Edge &rhs) const {
        return dis<rhs.dis;
    }
} edge[N],tr[N];
int n,m;
int father[N];
int G[N][N];
int tot,bel[N],val[N];
int Find(int x) {
    if(father[x]!=x)
        return father[x]=Find(father[x]);
    return x;
}
int Gauss(int n) {
    int res=1;
    for(int i=1; i<=n; i++) {
        for(int k=i+1; k<=n; k++) {
            while(G[k][i]) {
                int d=G[i][i]/G[k][i];
                for(int j=i; j<=n; j++)
                    G[i][j]=(G[i][j]-1LL*d*G[k][j]%MOD+MOD)%MOD;
                swap(G[i],G[k]);
                res=-res;
            }
        }
        res=1LL*res*G[i][i]%MOD,res=(res+MOD)%MOD;
    }
    return res;
}
int Kruskal() {
    sort(edge+1,edge+m+1);
    for(int i=1; i<=n; i++)
        father[i]=i;
    int cnt=0;
    for(int i=1; i<=m; i++) {
        int fu=Find(edge[i].x);
        int fv=Find(edge[i].y);
        if(fu==fv)
            continue;
        father[fu]=fv,tr[++cnt]=edge[i];
        if(edge[i].dis!=val[tot])
            val[++tot]=edge[i].dis;
    }
    return cnt;
}
void addTreeEdge(int v) {
    for(int i=1; i<n&&tr[i].dis!=v; i++){
        int x=tr[i].x;
        int y=tr[i].y;
        father[Find(x)]=Find(y);
    }
    for(int i=n-1; i&&tr[i].dis!=v; i--){
        int x=tr[i].x;
        int y=tr[i].y;
        father[Find(x)]=Find(y);
    }
}
void rebuild(int v) {
    memset(G,0,sizeof(G));
    for(int i=1; i<=m; i++){
        if(edge[i].dis==v){
            int x=bel[edge[i].x];
            int y=bel[edge[i].y];
            G[x][y]--;
            G[y][x]--;
            G[x][x]++;
            G[y][y]++;
        }
    }
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++)
        scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].dis);

    int cnt=Kruskal();
    if(cnt!=n-1) {
        printf("0\n");
    }
    else{
        int res=1;
        for(int i=1; i<=tot; i++) {
            for(int i=1; i<=n; i++)
                father[i]=i;

            addTreeEdge(val[i]);

            int blo=0;
            for(int i=1; i<=n; i++)
                if(Find(i)==i)
                    bel[i]=++blo;
            for(int i=1; i<=n; i++)
                bel[i]=bel[Find(i)];

            rebuild(val[i]);
            res=1LL*res*Gauss(blo-1)%MOD;
        }
        printf("%d\n",res);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值