1. 程式人生 > 實用技巧 >P4208 [JSOI2008]最小生成樹計數

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;
}