zoukankan      html  css  js  c++  java
  • [JSOI2008]最小生成树计数

    1016: [JSOI2008]最小生成树计数

    Time Limit: 1 Sec  Memory Limit: 162 MB
    Submit: 1027  Solved: 384

    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,000。数据保证不会出现自回边和重边。 注意:具有相同权值的边不会超过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
    ***************************************************************************
    题目大意:不废话。
    解题思路:这题的解题报告网上多的是,那个方法,我就不解释了,我表示我的方法有点不同,虽然比网上那个方法慢,不过如果数据强了,我的方法不会超时,表示挺爽。
    首先,最小生成树,我用的是kruskal,像别的地方一样,把边权相同的边当成一个边组。
    同一个图的不同的最小生成树之间是有相同的性质:
      1.每个边组中用的边的个数是一样的;
      2.同样做完i个边组的kruskal后形成的联通分量相同。
    我没找到什么太好的证明,自己的理解是,假设当已经做完了k-1个边组的kruskal形成了一个森林,然后在做第k边组的时候用了n个边,
    那么说明这个组别里面必须用n个边。换句话说,两个最小生成树在做完k-1个边组的时候还是一样的,在做完k的边组的时候产生了差异,MST1在k的边组时加入的边集是E1,MST2在k的边组时加入的边集是E2,那么E1的大小和E2必定一样。证明:反证法,不妨设E1比E2小,那么一定存在一条边e属于E2不属于E1,而如果e不属于E1,根据kruskal的算法过程,说明E1之中的某些边和e合在一起会形成环(不然e就可以加入E1中去,与kruskal矛盾),那么这个环中除了e这条边一定存在一条边e‘加入E2的话会形成环,e’是属于E1而不属于E2的,那么可以说e可以找到对应边e‘,边的对应性貌似是可以传递的,那么就不存在E2中的两条边对应E1中的一条边,这个可以画图理解,换句话说,每一条属于E2不属于E1的边都能找到对应点,那么E2的边的个数就和E1的一样。得证。同时,第二个性质也能证明了:每条e被e’替代,只是环拆掉了一条不同的边,这个环上的点依旧是联通的,那么不改变联通分量。
    有了这两个性质,最小生成树计数就好办了。网上传统方法是对于每一个边组进行2^n的暴力,枚举边的组成,然后不同的边组之间相乘。因为题目说明具有相同权值的边不会超过10条。这样暴力完全没关系。
    不过我的方法就不同了,复杂度应该在n^3左右,这个涉及到平摊时间复杂度了,我不会算。
    我的方法:在做k的边组的时候,如果做完后是a,b,c这3个原来的联通分量合成了一个d,那么无论怎么样的MST都是a,b,c合成了一个d,这其实是生成树计数,利用那个生成树计数的矩阵,轻松搞定,这道题如果它说相同的边可能有50个,那,我这个方法就是必须的了,嘿嘿。
    //#pragma comment(linker, "/STACK:65536000")
    #include <map>
    #include <stack>
    #include <queue>
    #include <math.h>
    #include <vector>
    #include <string>
    #include <fstream>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <iostream>
    #include <algorithm>
    #define N 105
    #define M 1005
    #define E
    #define inf 0x3f3f3f3f
    #define dinf 1e10
    #define linf (LL)1<<60
    #define LL long long
    #define clr(a,b) memset(a,b,sizeof(a))
    using namespace std;
    
    const int mod = 31011;
    struct Edge
    {
        int a,b,c;
        bool operator<(const Edge & t)const
        {
            return c<t.c;
        }
    }edge[M];
    int n,m,ans;
    int fa[N],ka[N],vis[N];//fa,ka都是并查集,ka是每组边临时使用
    LL gk[N][N],tmp[N][N];//gk顶点之间的关系,tmp为生成树计数用的矩阵
    vector<int>gra[N];
    
    int findfa(int a,int b[]){return a==b[a]?a:b[a]=findfa(b[a],b);}
    
    LL det(LL a[][N],int n)//生成树计数
    {
        for(int i=0;i<n;i++)for(int j=0;j<n;j++)a[i][j]%=mod;
        int ret=1;
        for(int i=1;i<n;i++)
        {
            for(int j=i+1;j<n;j++)
                while(a[j][i])
                {
                    LL t=a[i][i]/a[j][i];
                    for(int k=i;k<n;k++)
                        a[i][k]=(a[i][k]-a[j][k]*t)%mod;
                    for(int k=i;k<n;k++)
                        swap(a[i][k],a[j][k]);
                    ret=-ret;
                }
            if(a[i][i]==0)return 0;
            ret=ret*a[i][i]%mod;
        }
        return (ret+mod)%mod;
    }
    
    int main()
    {
        //freopen("/home/axorb/in","r",stdin);
    	scanf("%d%d",&n,&m);
    	for(int i=0;i<m;i++)
            scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].c);
        sort(edge,edge+m);
        for(int i=1;i<=n;i++)fa[i]=i,vis[i]=0;
        int pre=-1;ans=1;
        for(int h=0;h<=m;h++)
        {
            if(edge[h].c!=pre||h==m)//一组边加完
            {
                for(int i=1;i<=n;i++)
                    if(vis[i])
                    {
                        int u=findfa(i,ka);
                        gra[u].push_back(i);
                        vis[i]=0;
                    }
                for(int i=1;i<=n;i++)//枚举每个联通分量
                    if(gra[i].size()>1)
                    {
                        for(int a=1;a<=n;a++)
                            for(int b=1;b<=n;b++)
                                tmp[a][b]=0;
                        int len=gra[i].size();
                        for(int a=0;a<len;a++)//构建矩阵
                            for(int b=a+1;b<len;b++)
                            {
                                int la=gra[i][a],lb=gra[i][b];
                                tmp[a][b]=(tmp[b][a]-=gk[la][lb]);
                                tmp[a][a]+=gk[la][lb];tmp[b][b]+=gk[la][lb];
                            }
                        int ret=(int)det(tmp,len);
                        ans=(ans*ret)%mod;
                        for(int a=0;a<len;a++)fa[gra[i][a]]=i;
                    }
                for(int i=1;i<=n;i++)
                {
                    ka[i]=fa[i]=findfa(i,fa);
                    gra[i].clear();
                }
                if(h==m)break;
                pre=edge[h].c;
            }
            int a=edge[h].a,b=edge[h].b;
            int pa=findfa(a,fa),pb=findfa(b,fa);
            if(pa==pb)continue;
            vis[pa]=vis[pb]=1;
            ka[findfa(pa,ka)]=findfa(pb,ka);
            gk[pa][pb]++;gk[pb][pa]++;
        }
        int flag=0;
        for(int i=2;i<=n&&!flag;i++)if(ka[i]!=ka[i-1])flag=1;
        printf("%d\n",flag?0:ans);
    	return 0;
    }
    

      

    也许有挫折,但这些,怎能挡住湘北前进的步伐
  • 相关阅读:
    Spring整合JMS-基于activeMQ实现(二)
    iOS 2D绘图详解(Quartz 2D)之概述
    iOS开发UI-利用Quartz2D 实现基本绘图(画三角形、矩形、圆、圆弧)
    Quart 2D 绘制图形简单总结
    IOS用CGContextRef画各种图形(文字、圆、直线、弧线、矩形、扇形、椭圆、三角形、圆角矩形、贝塞尔曲线、图片)
    用 Swift 制作一个漂亮的汉堡按钮过渡动画
    CAShapeLayer和CAGradientLayer
    Swift计算属性
    Swift常用语法示例代码(二)
    Swift 中的指针使用
  • 原文地址:https://www.cnblogs.com/Fatedayt/p/2494877.html
Copyright © 2011-2022 走看看