【JSOI 2008】 最小生成树计数

【题目链接】

           点击打开链接

【算法】

          笔者做这题参考了这篇博客 :

          https://blog.sengxian.com/solutions/bzoj-1016

          推荐阅读

          首先,我们需要知道三个定理 :

          定理1 : 若A,B是两棵不同的最小生成树,它们的权值从小到大排列分别为 :

                        W(a1),W(a2),W(a3)....W(an-1)

                        W(b1),W(b2),W(b3)....W(bn-1)

                        那么,对于任意的i,都有W(ai) = W(bi)

          定理2 : 当最小生成树中所有w <= w0的边被加入后,图的联通性唯一

          定理3 : 若A是一棵最小生成树,A中权值为v的边有k条,那么,用任意k条权值为v的边替换A中权值为v的边且不产生

                        环的方案都是一棵最小生成树

         证明详见笔者推荐的那篇博客

         有了这三个定理,这题就很好做啦! 首先,任意求一棵最小生成树,记录每种权值的边出现的次数,然后,对每种

          权值的边做一遍深度优先搜索DFS,求出方案数,然后乘法原理,即可

【代码】

            注意因为进行DFS时需要回溯,所以,并查集不能路径压缩

#include<bits/stdc++.h>
using namespace std;
#define MAXN 110
#define MAXM 1010
const int MOD = 31011;

int n,m,i,j,ans = 1,pos,len,sum,sx,sy;
int fa[MAXN],s[MAXN],val[MAXN],l[MAXN],r[MAXN];

struct Edge
{
        int u,v,w;
} e[MAXM];
inline void init(int n)
{
        int i;
        for (i = 1; i <= n; i++) fa[i] = i;
}
int get_root(int x)
{
        if (fa[x] == x) return x;
        return get_root(fa[x]);
}    

bool cmp(Edge a,Edge b)
{
        return a.w < b.w;
}
bool kruskal()
{
        int i,cnt = 0,sx,sy;
        for (i = 1; i <= m; i++)
        {
                sx = get_root(e[i].u);
                sy = get_root(e[i].v);
                if (e[i].w == val[len]) r[len]++;
                if (sx != sy)
                {
                        fa[sx] = sy;
                        cnt++;
                        if (e[i].w == val[len]) s[len]++;
                        else
                        {
                                len++;
                                l[len] = r[len] = i;
                                s[len]++;
                                val[len] = e[i].w;
                        }
                }
        }            
        return cnt == n - 1;    
}
inline void dfs(int now,int r,int c)
{
        int sx,sy;
        if (now > r)
        {
                if (c == s[pos]) sum++;
                return;
        }
        dfs(now+1,r,c);
        sx = get_root(e[now].u);
        sy = get_root(e[now].v);
        if (sx != sy) 
        {
                fa[sx] = sy;
                dfs(now+1,r,c+1);
                fa[sx] = sx;
        }
}

int main() {
        
        scanf("%d%d",&n,&m);
        for (i = 1; i <= m; i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
        sort(e+1,e+m+1,cmp);
        init(n);
        if (!kruskal())
        {
                puts("0");
                return 0;
        }
        init(n);
        for (i = 1; i <= len; i++)
        {
                sum = 0;
                pos = i;
                dfs(l[i],r[i],0);
                ans = (ans * sum) % MOD;
                for (j = l[i]; j <= r[i]; j++)
                {
                        sx = get_root(e[j].u);
                        sy = get_root(e[j].v);
                        if (sx != sy) fa[sx] = sy;        
                }                    
        }
        printf("%d\n",ans);
        
        return 0;
    
}

 

转载于:https://www.cnblogs.com/evenbao/p/9196317.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值