zoukankan      html  css  js  c++  java
  • 洛谷P4208 [JSOI2008]最小生成树计数——题解

    题目传送

     

     

     

      前置知识:对于同一个图的所有最小生成树,权值相等的边的数量相同。

      可以简单证明一下:

        我们可以从kruskal的过程考虑。这个算法把所有边按权值大小从小到大排序,然后按顺序看每条边,只要加上这条边后不会形成连通块,就加上。

        以上过程其实等价于先将所有权值等于第一条边的边都加进图中,然后一个个删边,使图中无环。设权值等于第一条边的边数为i,下次再将所有权值等于第i+1条边的边都加进图中。。。直至算过最后一条边,或图中刚好剩下了n-1条边(n为图的点的个数)。

        发现加完一批边后要删的边的个数等于形成的“最小环”的个数(这里最小环是指:对于一个最小环,不存在一组边使得通过这组边把环“从中间切开”后,被切开的环的两部分可与这组边形成两个新环(即不是依照国际标准的定义,而是为了方便在本文现定义的);同时最小环边数不一定小)。

        为什么呢?从一个最小环开始考虑:

          若不存在其他的某个最小环v与这个环u有公共边,那么只要任意删一条边就能减少一个最小环。

          若存在,这时删边就有两种情况:

            删公共边:首先u和v原来的最小环形态都会被破坏,最小环数目-2。然后,发现u和v剩下的部分又可以组成一个新的最小环,所以最小环的数目又+1。所以最小环数目-1;

            不删公共边:u的最小环形态被破坏,且不会生成新的最小环,所以最小环数目-1。

          综上,可知要删的边的数目==最小环的数目,且要删的边可是最小环上的任意边。

        由于加完一批边后,最小环的数目确定,所以删的边的数目也确定。故图生成的所有最小生成树边权相等的边数目也相等。所以我们可以先跑一次最小生成树,记录下每种边权在最小生成树中的出现次数

      同时我们还发现,当处理完一批权值等于x的边后,这个图的连通性(即都有哪些点连通)是唯一的。即使不用kruskal做最小生成树,设用了算法A做最小生成树,如果只保留权值等于x的边,那么保留的图的连通性与用kruskal做到处理完权值等于x的边时是一样的。否则,只可能连通的点数小于用kruskal做到时的连通的点数(因为kruskal全部地考虑过了权值等于x的边,其他算法不可能比全部还多吧)。但这是不成立的,因为若成立,就说明用kruskal做的最小生成树X中权值等于x的边比A算法得到的最小生成树Y中权值等于x的边的边数多一。由于kruskal是从小边开始贪心考虑所有边的,那么X的权值和一定小于Y,与Y是最小生成树矛盾;并且这也与上文的定理矛盾。

      设最小生成树边的权值从小到大分别为x1,x2,...,xk,那么构造一个最小生成树只需要分k步,每步都是在前面步骤都做了的基础上,选择一个对所有权值等于x的边的保留方案。由于每步造成的对连通性的影响都是一样的,即每步的结果都是一样的,所以可以用乘法原理,将每步的保留方案数乘起来再去模就是答案了。

      怎么求每步的保留方案数呢?由于题目限制权值相等的边不超过10条,所以用dfs枚举就是了,同时可用并查集验证可行性。注意:如果dfs要回溯到之前状态,那么并查集不能路径压缩,否则并查集的状态难以回溯到之前的状态。

    代码:

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<algorithm>
      4 
      5 using namespace std;
      6 
      7 const int N=105,M=1005,mod=31011;
      8 
      9 struct Edge{
     10     int from,to,len;
     11 }e[M];
     12 
     13 int n,m,f[N],l[M],r[M],cnt,tot,ecnt[M];
     14 int x;
     15 
     16 char ch;
     17 
     18 inline int read()
     19 {
     20     x=0;
     21     ch=getchar();
     22     while(!isdigit(ch)) ch=getchar();
     23     while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
     24     return x;
     25 }
     26 
     27 inline bool cmp(const Edge &a,const Edge &b)
     28 {
     29     return a.len<b.len;
     30 }
     31 
     32 int yfa(int u)
     33 {
     34     if(u==f[u]) 
     35         return u;
     36     else 
     37         return f[u]=yfa(f[u]);
     38 }    
     39 
     40 int fa(int u)
     41 {
     42     if(u==f[u])
     43         return u;
     44     else
     45         return fa(f[u]);
     46 }
     47 //能/不能路径压缩的并查集查找 
     48 void dfs(int wei,int kin,int had)//当前看的边数组的位置,当前看的边的种类,当前已经取的边的数目 
     49 {
     50     int b1,b2;
     51     b1=fa(e[wei].from);//dfs要回溯状态,所以dfs里并查集查找操作不能路径压缩 
     52     b2=fa(e[wei].to);
     53     if(r[kin]-wei+1+had==ecnt[kin])//如果已经取的边的数目+还能取的数目=要取的边的数目,就只能取了 
     54     {
     55         if(b1==b2) return;
     56         if(had==ecnt[kin]-1) 
     57         {
     58             cnt++;
     59             return;
     60         }
     61         f[b1]=b2;
     62         dfs(wei+1,kin,had+1);
     63         f[b1]=b1;
     64         return;
     65     }
     66     if(b1!=b2)
     67     {
     68         if(had+1==ecnt[kin])
     69             cnt++;
     70         else
     71         {
     72             f[b1]=b2;
     73             dfs(wei+1,kin,had+1);
     74             f[b1]=b1;
     75         }
     76     }
     77         dfs(wei+1,kin,had);
     78 }
     79 
     80 int main()
     81 {
     82     n=read(),m=read();
     83     for(int i=1;i<=m;++i)
     84         e[i].from=read(),e[i].to=read(),e[i].len=read();
     85     sort(e+1,e+m+1,cmp);
     86     for(int i=1;i<=n;++i)
     87         f[i]=i;
     88     int u,v;
     89     for(int i=1;i<=m;++i)
     90     {
     91         if(e[i].len==e[i-1].len)
     92         {
     93             r[tot]++;
     94         }
     95         else
     96         {
     97             ++tot;
     98             l[tot]=r[tot]=i;
     99         }
    100         if(cnt<n-1)
    101         {
    102             u=yfa(e[i].from),v=yfa(e[i].to);
    103             if(u!=v)
    104             {
    105                 ecnt[tot]++;
    106                 f[u]=v;
    107                 cnt++;
    108             }
    109         }
    110     }
    111     if(cnt<n-1)//注意无解时的判断 
    112     {
    113         printf("0");
    114         return 0;
    115     }
    116     long long ans=1;
    117     for(int i=1;i<=n;++i) f[i]=i;
    118     for(int i=1;i<=tot;++i) 
    119         if(ecnt[i])
    120         {
    121             cnt=0;
    122             dfs(l[i],i,0);
    123             ans=(ans*cnt)%mod;
    124             for(int j=l[i];j<=r[i];++j)//更新当前步骤做完时图的连通性 
    125             {
    126                 u=yfa(e[j].from);
    127                 v=yfa(e[j].to);
    128                 if(u!=v) 
    129                     f[u]=v;
    130             }
    131         }
    132     printf("%lld",ans);
    133     return 0;
    134 }

     

  • 相关阅读:
    剑指 Offer 22. 链表中倒数第k个节点(简单)
    剑指 Offer 18. 删除链表的节点(简单)
    Proxy error: Could not proxy request
    剑指 Offer 63. 股票的最大利润(中等)
    剑指 Offer 47. 礼物的最大价值(中等)
    剑指 Offer 42. 连续子数组的最大和(简单)
    oracle 常用函数之 字符函数
    oracle 常用函数之 日期函数
    oracle 常用函数之聚合函数
    jdbc
  • 原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/11742332.html
Copyright © 2011-2022 走看看