最小生成树_bzoj1016

题目意思

题目中要求求出不同的最小生成树的个数     bzoj 1016

题目解析

这里我们使用kruskal算法来求解最小生成树,它的基本思路为:根据路径权值由小到大进行排序,然后每次都拿出还没有判断的路径中权值最小的,判断两端点是否在同一连通分量中,如果在同一连通分量中,不做任何操作,否则将这条边加入到最小生成树中

那么我们如果寻找其他最小生成树的话,含有就是在选择相同路径权值的情况下,选择连通性和最终情况不会发生改变的路径和当前进行替换

我们进行第一遍kruskal的时候记录下,每种权值的路径需要多少(他的含义可以理解为:需要连接多少不用的连通分量),那么我们在寻找等价的最小生成树的时候,我们只需要找到相同条数连接不同连通分量的边就可以了,因为在当前权值的路径下,我们的最终状态已经是固定的了,那么只要选出来和最初相同条数的边连接不同的连通分量就可以到达我们的最终状态了

那么我们这里怎么寻找这些相同条连接不同连通分量的边呢?(权值相同的边)

这里题中给出说明:具有相同权值的边不会超过10,数据很小,我们直接可以用dfs进行回溯判断就可以了!

这里我们要注意:不能使用路径压缩,因为如果选择了这条边,那么后面状态就不能进行恢复了,然后每当我们遍历完一种权值的时候,我们就要更新前一个权值的父亲数组

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;
//因为为最小生成树,根据Kruskal的特性,我们选择完一个长度的边之后,我们的连通情况就已经是定下的情况了,因为如果还有其他的连通情况的话,必定会在当前边判定情况下添加进去
//那么多个最小生成树也就是指当前边在保证连通性不会发生变化的情况下,选择其他路径长度相同的边进行替换,每条边替换一次,将每条边的情况数相乘得到最终的答案!
const int MOD = 31011;
const int MAXN = 1100;
int n,m;
struct Edge
{
    int u,v,w;
    bool operator < (const Edge & a)const
    {
        return w<a.w;
    }
}edge[MAXN];
vector<Edge> v[MAXN];
int cnt[MAXN],tot,per[MAXN];
void Init()
{
    tot = 0;
    memset(cnt,0,sizeof(cnt));
    for(int i = 1;i <= n;i ++)
        per[i] = i;
}
int Find(int x)
{
    //return x==per[x]?x:x = Find(per[x]);
    if(x == per[x]) return x;
    per[x] = Find(per[x]);
    return per[x];
}
int Find2(int x)
{
    if(x == per[x]) return x;
    return Find2(per[x]);
}

int Dfs(int k,int p,int now)
{
    //k表示选取路径的种类
    //p表示选取当前长度中的第几个
    //now表示当前长度中已经选取的长度,最多为cnt[k]
    if(p == v[k].size()) return now == cnt[k];   //返回当前情况是否符合条件!

    int ret = 0,x,y;
    if(now < cnt[k])//当前长度最多选取的个数,选取当前边权
    {
        x = Find2(v[k][p].u);
        y = Find2(v[k][p].v);
        if(x != y)  //这里就会保证连通性不发生变化!也就是只要选出cnt[k]个边来,就满足连通性要求!
        {
            per[x] = y;
            ret += Dfs(k,p+1,now+1);
            per[x] = x;//复原之前的更改
        }
    }
    if(now+v[k].size()-p-1 >= cnt[k]) ret += Dfs(k,p+1,now);//不选当前边权(前题为后面的个数还能够满足最少的情况!)
    return ret;
}
int main()
{
    int now = 0;
    scanf("%d%d",&n,&m);
    Init();
    edge[0].w = 0;
    for(int i = 1;i <= m;i ++)
        scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
    sort(edge+1,edge+m+1);
    for(int i = 1;i <= m;i ++)
    {
        if(edge[i].w > edge[i-1].w) tot ++;
        v[tot].push_back(edge[i]);   //存储的为当前边权总的个数
        int x = Find(edge[i].u);
        int y = Find(edge[i].v);
        if(x != y)
        {
            per[x] = y;
            cnt[tot]++;
            now ++;
        }
    }//首先用kruskal找出第一个最小生成树
    if(now < n-1)//最小生成树为0的情况!
    {
        printf("0\n");
        return 0;
    }
    int ans = 1;
    for(int i = 1;i <= n;i ++) per[i] = i;
    for(int i = 1;i <= tot;i ++)
    {
        if(cnt[i])
        {
            ans = (ans * Dfs(i,0,0)) % MOD;
            for(int j  = 0;j < v[i].size();j ++)//更新这个边添加之后的状态!
            {
                int x = Find(v[i][j].u);
                int y = Find(v[i][j].v);
                per[x] = y;
            }
        }

    }
    printf("%d\n",ans);
    return 0;
}

当然这里有说明形同的边数不会超过10条,可以使用深搜直接解题,但是如果相同边数很多的话,使用深搜就一定会超时的.....那么当相同边数很多的话,我们还有一种其他的做法,后面有时间再进行整理!

参考博客

http://hzwer.com/3005.html

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/80398516