zoukankan      html  css  js  c++  java
  • 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
    ");
     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
    ",ans);
     88 }
  • 相关阅读:
    视频输入 范例
    视频输出 范例
    开启VI视频输入设备 范例
    初始化MMP系统 范例
    Git 的使用
    DVS/DVR/NVR/XVR
    shell命令中 && 和 || 的区别
    码流 / 码率 / 比特率 / 帧速率 / 分辨率 / 高清
    DNS与DSN
    ob_start()失效与phpunit的非正常结束
  • 原文地址:https://www.cnblogs.com/liuzhanshan/p/6874690.html
Copyright © 2011-2022 走看看