首页 > 代码库 > BZOJ1016 [JSOI2008]最小生成树计数

BZOJ1016 [JSOI2008]最小生成树计数

Description

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

Input

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

Output

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

Sample Input

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

Sample Output

8

HINT

Source

分析:

首先需要一个结论,对于一个图的不同最小生成树,每种方案所包含的每种权值的边的数量一定一致。

换句话说,把每种方案包含的所有边的边权都写下来,写出来的序列一定都一样。

证明:

考虑kruskal的过程,开始时,每个点单独构成一个集合。

首先只考虑权值最小的边,将它们全部添加进图中(记作步骤1)。

那么现在的图有若干部分是联通的了。这就是添加完最短边后,能够形成的最多的联通分量的数目,注意,根据Kruskal,也是这种权值的边添加完成之后的联通分量数目(贪心原则)

现在我们得出一个结论,添加完最小权值的边后,最终形成的联通分量是一定的,且集合的划分情况一定相同。

那么真正添加的边数也是相同的。因为每添加一条边集合的数目便减少1,不可能出现添加一条边集合数目不变或减少2的情况。

那么权值第二小的边呢?我们将之间得到的集合每个集合都缩为一个点,那么权值第二小的边就变成了当前权值最小的边,也有上述的结论。

因此每个阶段,添加的边数都是相同的。我们以权值划分阶段,那么也就意味着某种权值的边的数目是完全相同的。

根据上面这个结论,我们只需要用Kruskal求一遍最小生成树,统计每种权值的边出现的次数。

然后dfs对每一种权值的边有多少种选边方法即可

另外,可以看一下周冬的OI集训队论文《生成树的计数及其应用》,里面有更厉害的解法

代码:

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<vector>
  4 #include<algorithm>
  5 using namespace std;
  6 const int mod=31011;
  7 struct edge
  8 {
  9     int x,y,l;
 10     bool operator < (const edge &e) const
 11     {
 12         return l<e.l;
 13     }
 14 }g[1010];
 15 vector<edge> v[1010];
 16 int cnt[1010],f[110],tot;
 17 int find(int x)
 18 {
 19     if (x==f[x]) return x;
 20     f[x]=find(f[x]);
 21     return f[x];
 22 }
 23 int find2(int x)
 24 {
 25     if (x==f[x]) return x;
 26     return find2(f[x]);
 27 }
 28 int dfs(int k,int p,int now)
 29 {
 30     if (p==v[k].size())
 31       return now==cnt[k];
 32     int ret=0,x,y,z;
 33     if (now<cnt[k])
 34     {
 35         x=find2(v[k][p].x);
 36         y=find2(v[k][p].y);
 37         if (x!=y)
 38         {
 39             f[x]=y;
 40             ret+=dfs(k,p+1,now+1);
 41             f[x]=x;
 42         }
 43     }
 44     if (now+v[k].size()-p-1>=cnt[k]) ret+=dfs(k,p+1,now);
 45     return ret;
 46 }
 47 int main()
 48 {
 49     int i,j,k,m,n,p,q,x,y,z,now=0,ans;
 50     scanf("%d%d",&n,&m);
 51     for (i=1;i<=m;i++)
 52       scanf("%d%d%d",&g[i].x,&g[i].y,&g[i].l);
 53     sort(g+1,g+m+1);
 54     for (i=1;i<=n;i++)
 55       f[i]=i;
 56     for (i=1;i<=m;i++)
 57     {
 58         if (g[i].l>g[i-1].l) tot++;
 59         v[tot].push_back(g[i]);
 60         x=find(g[i].x);
 61         y=find(g[i].y);
 62         if (x!=y)
 63         {
 64             cnt[tot]++;
 65             f[x]=y;
 66             now++;
 67         }
 68     }
 69     if (now<n-1)
 70     {
 71         printf("0\n");
 72         return 0;
 73     }
 74     ans=1;
 75     for (i=1;i<=n;i++)
 76       f[i]=i;
 77     for (i=1;i<=tot;i++)
 78     {
 79         ans=(ans*dfs(i,0,0))%mod;
 80         for (j=0;j<v[i].size();j++)
 81         {
 82             x=find(v[i][j].x);
 83             y=find(v[i][j].y);
 84             f[x]=y;
 85         }
 86     }
 87     printf("%d\n",ans);
 88 }

BZOJ1016 [JSOI2008]最小生成树计数