洛谷P4208 [JSOI2008]最小生成树计数——题解

题目传送门
题目大意:如题,求一个图中最小生成树的个数


思考过程:
我们考虑用kruskal求最小生成树的过程,首先将所有边按权值从小到大排序,顺着往后扫,如果起点和终点所处的并查集不一样,就将他们合并,若最后所选边数等于 n1 n − 1 ,则存在最小生成树。
我们将这个过程变换一下,就可以用来求解这道题了。
我们可以在选边时顺便统计每个不同权值的边各用了多少条,然后求出在所有该权值的边中选这么多条能达到目的的方案有多少种,最后乘法原理乘起来即可。


具体做法:
1.用a记录每个权值的边的开始位置、结束位置和所选条数
2.分别dfs每个权值的边求出方案数
3.乘法原理


代码:

#include 
using namespace std;

const int maxn=110,maxm=1100,mod=31011;
struct stu
{
    int u,v,w;  
}road[maxm],a[maxm];
int n,m,cnt,tot,sum,ans=1;
int fa[maxn];

bool cmp(stu t1,stu t2)
{
    return t1.wint find(int x)
{
    if(x==fa[x]) return x;
    return find(fa[x]); //注意此处一定不能写成 return fa[x]=find(fa[x])
                        //形象地理解就是不能让一个并查集里的元素通过路径压缩根深蒂固地粘在一起
                        //否则dfs回溯过程中就无法用 fa[p1]=p1,fa[p2]=p2 分开 
}

void dfs(int now,int used,int x)
{
    if(now==a[x].v+1)
    {
        if(used==a[x].w) sum++;
        return; 
    }
    int p1=find(road[now].u),p2=find(road[now].v);
    if(p1!=p2)
    {
        fa[p1]=p2;
        dfs(now+1,used+1,x);
        fa[p1]=p1,fa[p2]=p2;    
    }
    dfs(now+1,used,x);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&road[i].u,&road[i].v,&road[i].w);
    sort(road+1,road+1+m,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        if(road[i].w!=road[i-1].w) { a[++cnt].u=i;a[cnt-1].v=i-1; }
        int p1=find(road[i].u),p2=find(road[i].v);
        if(p1!=p2)
        {
            fa[p1]=p2;
            a[cnt].w++;
            tot++;  
        }
    }
    a[cnt].v=m;
    if(tot!=n-1) { printf("0\n"); return 0; }
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=cnt;i++)
    {
        if(!a[i].w) continue;
        sum=0;
        dfs(a[i].u,0,i);
        ans=(ans*sum)%mod;
        for(int j=a[i].u;j<=a[i].v;j++)
        {
            int p1=find(road[j].u),p2=find(road[j].v);
            if(p1!=p2) fa[p1]=p2;   
        }
    }
    printf("%d\n",ans);
    return 0;
}   

你可能感兴趣的:(题解)