D 1016: [JSOI2008]最小生成树计数 (最小生成树个数)

50 篇文章 0 订阅

题目描述

现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。

输入

第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

输出

输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

样例输入

4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1

样例输出

8

题解:

就是不同的最小生成树方案,每种权值的边的数量是确定的,每种权值的边的作用是确定的

排序以后先做一遍最小生成树,得出每种权值的边使用的数量x

然后对于每一种权值的边搜索,得出每一种权值的边选择方案

然后乘法原理(做一件事,完成它需要分成n个步骤,做第一 步有m1种不同的方法,做第二步有m2不同的方法,……,做第n步有mn不同的方法。那么完成这件事共有 N=m1×m2×m3×…×mn 种不同的方法。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 31011
using namespace std ;
int n , m , cnt , tot , ans = 1 , sum ;
int fa [ 105 ] ;
struct edge { int x , y , v ; } e [ 1005 ] ;
struct data { int l , r , v ; } a [ 1005 ] ;
inline int read ( )
{
     int x = 0 ; char ch = getchar ( ) ;
     while ( ch < '0' || ch > '9' ) { ch = getchar ( ) ; }
     while ( ch >= '0' && ch <= '9' ) { x = x * 10 + ch - '0' ; ch = getchar ( ) ; }
     return x ;
}
bool cmp ( edge a , edge b ) { return a . v < b . v ; }
int find ( int x ) { return x == fa [ x ] ? x : find ( fa [ x ] ) ; }
void dfs ( int x , int now , int k )//只要符合条件(k==a[x].v),那这里面出现的点必定联通,即效果是一样的
{
     if ( now == a [ x ] . r + 1 )
     {
         if ( k == a [ x ] . v ) sum ++ ;
         return ;
     }
     int p = find ( e [ now ] . x ) , q = find ( e [ now ] . y ) ;
     if ( p != q )
     {
         fa [ p ] = q ;
         dfs ( x , now + 1 , k + 1 ) ;
         fa [ p ] = p ; fa [ q ] = q ;
     }
     dfs ( x , now + 1 , k ) ;
}
int main ( )
{
     n = read ( ) ; m = read ( ) ;
     for ( int i = 1 ; i <= n ; i ++ )
         fa [ i ] = i ;
     for ( int i = 1 ; i <= m ; i ++ )
         e [ i ] . x = read ( ) , e [ i ] . y = read ( ) , e [ i ] . v = read ( ) ;
     sort ( e + 1 , e + m + 1 , cmp ) ;
     for ( int i = 1 ; i <= m ; i ++ )
     {
         if ( e [ i ] . v != e [ i - 1 ] . v ) { a [ ++ cnt ] . l = i ; a [ cnt - 1 ] . r = i - 1 ; }
         int p = find ( e [ i ] . x ) , q = find ( e [ i ] . y ) ;
         if ( p != q ) { fa [ p ] = q ; a [ cnt ] . v ++ ; tot ++ ; }
     }
     a [ cnt ] . r = m ;
     if ( tot != n - 1 ) { printf ( "0" ) ; return 0 ; }
     for ( int i = 1 ; i <= n ; i ++ )
         fa [ i ] = i ;
     for ( int i = 1 ; i <= cnt ; i ++ )
     {
         sum = 0 ;
         dfs ( i , a [ i ] . l , 0 ) ;
         ans = ( ans * sum ) % mod ;
         for ( int j = a [ i ] . l ; j <= a [ i ] . r ; j ++ )
         {
             int p = find ( e [ j ] . x ) , q = find ( e [ j ] . y ) ;
             if ( p != q ) fa [ p ] = q ;
         }//会觉得有问题吗??为什么在这里出现的点之后一定联通?想一下,如果不连通的话,是不是要用之后的边来联通?要知道,边已经按权值大小排列好了。
     }
     printf ( "%d" , ans ) ;
     return 0 ;
}

最后,你会不会觉得奇怪,为什么都是 一样的边权,一样的边数 ?1+4和2+3不行吗??好吧,那为什么不选择1+3(这只是简单的例子,复杂的可以借助这个解决)。。。好好想想~~~



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值