P4208 [JSOI2008]最小生成树计数

思路

刚看到的时候,因为 \((n\leq 100)\) ,所以想到了爆搜,但是这样做显然会 \(TLE\) ,所以我们手摸几组数据找找结论

然后能发现一个结论:一张图上的不同最小生成树中,权值相等的边的个数是不变的

小证明:用kruskal求最小生成树时,每一步都是最优的,如果有不同的最小生成树,则当前步的权值必然小于等于之前最小生成树当前步的选择。但是反证可得,如果有小于的话,此时的最小生成树就比之前的优了,和之前矛盾,所以权值相等的边的个数是不变的。

代码

#include<bits/stdc++.h>

using namespace std;
const int mod=31011;
struct node{
    int from,to,w;
}e[1010];
struct kruskal{
    int l,r,v;
}a[1010];//存i边的个数,l r是左右端点,v是i在最小生成树上的个数
int n,m,f[110],ans,q[110],num,sum;

int find(int x){return x==f[x]?x:find(f[x]);}
//不能路径压缩!!!

bool cmp(node a,node b){
    return a.w<b.w;
}

void dfs(int x,int now,int k)//x是你当前找的值 now是第几个边 k是你选了的个数
{
    if(now==a[x].r+1){
        if(k==a[x].v) sum++;//保证和生成树所需的一样
        return ;
    }
    int xx=find(e[now].from),yy=find(e[now].to);
    if(xx!=yy)//看是否选这边就为环
    {
        f[xx]=yy;
        dfs(x,now+1,k+1);//选
        f[xx]=xx;f[yy]=yy;//复原
    }
    dfs(x,now+1,k);//不选
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].w);
    sort(e+1,e+m+1,cmp);
    int tot=0;
    for(int i=1;i<=m;i++){
        if(e[i].w!=e[i-1].w) num++,a[num].l=i,a[num-1].r=i-1;
        int xx=find(e[i].from),yy=find(e[i].to);
        if(xx!=yy) f[xx]=yy,a[num].v++,tot++;
    }//kruskal
    if(tot!=n-1){
        printf("0");
        return 0;
    }//如果构不成树,就可以输出0了
    a[num].r=m;
    ans=1;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=num;i++){
        sum=0;
        dfs(i,a[i].l,0);
        ans=(ans*sum)%mod;
        for(int j=a[i].l;j<=a[i].r;j++){
            int xx=find(e[j].from),yy=find(e[j].to);
            if(xx!=yy) f[xx]=yy;
        }//弄完一个后,连起来保证不为环
    }
    printf("%d",ans);
    return 0;
}
posted @ 2020-08-21 20:47  jasony_sam  阅读(124)  评论(0编辑  收藏  举报