[JSOI2008]最小生成樹計數


1016: [JSOI2008]最小生成樹計數

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 1027   Solved: 384

Description

現在給出了一個簡單無向加權圖。你不滿足於求出這個圖的最小生成樹,而希望知道這個圖中有多少個不同的最小生成樹。(如果兩顆最小生成樹中至少有一條邊不同,則這兩個最小生成樹就是不同的)。由於不同的最小生成樹可能很多,所以你只需要輸出方案數對31011的模就可以了。

Input

第一行包含兩個數,n和m,其中1<=n<=100; 1<=m<=1000; 表示該無向圖的節點數和邊數。每個節點用1~n的整數編號。 接下來的m行,每行包含兩個整數:a, b, c,表示節點a, b之間的邊的權值為c,其中1<=c<=1,000,000,000。數據保證不會出現自回邊和重邊。 注意:具有相同權值的邊不會超過10條。

Output

輸出不同的最小生成樹有多少個。你只需要輸出數量對31011的模就可以了。

Sample Input

4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1


Sample Output

8
***************************************************************************
題目大意:不廢話。
解題思路:這題的解題報告網上多的是,那個方法,我就不解釋了,我表示我的方法有點不同,雖然比網上那個方法慢,不過如果數據強了,我的方法不會超時,表示挺爽。
首先,最小生成樹,我用的是kruskal,像別的地方一樣,把邊權相同的邊當成一個邊組。
同一個圖的不同的最小生成樹之間是有相同的性質:
  1.每個邊組中用的邊的個數是一樣的;
  2.同樣做完i個邊組的kruskal后形成的聯通分量相同。
我沒找到什么太好的證明,自己的理解是,假設當已經做完了k-1個邊組的kruskal形成了一個森林,然后在做第k邊組的時候用了n個邊,
那么說明這個組別里面必須用n個邊。換句話說,兩個最小生成樹在做完k-1個邊組的時候還是一樣的,在做完k的邊組的時候產生了差異,MST1在k的邊組時加入的邊集是E1,MST2在k的邊組時加入的邊集是E2,那么E1的大小和E2必定一樣。證明:反證法,不妨設E1比E2小,那么一定存在一條邊e屬於E2不屬於E1,而如果e不屬於E1,根據kruskal的算法過程,說明E1之中的某些邊和e合在一起會形成環(不然e就可以加入E1中去,與kruskal矛盾),那么這個環中除了e這條邊一定存在一條邊e‘加入E2的話會形成環,e’是屬於E1而不屬於E2的,那么可以說e可以找到對應邊e‘,邊的對應性貌似是可以傳遞的,那么就不存在E2中的兩條邊對應E1中的一條邊,這個可以畫圖理解,換句話說,每一條屬於E2不屬於E1的邊都能找到對應點,那么E2的邊的個數就和E1的一樣。得證。同時,第二個性質也能證明了:每條e被e’替代,只是環拆掉了一條不同的邊,這個環上的點依舊是聯通的,那么不改變聯通分量。
有了這兩個性質,最小生成樹計數就好辦了。網上傳統方法是對於每一個邊組進行2^n的暴力,枚舉邊的組成,然后不同的邊組之間相乘。因為題目說明具有相同權值的邊不會超過10條。這樣暴力完全沒關系。
不過我的方法就不同了,復雜度應該在n^3左右,這個涉及到平攤時間復雜度了,我不會算。
我的方法:在做k的邊組的時候,如果做完后是a,b,c這3個原來的聯通分量合成了一個d,那么無論怎么樣的MST都是a,b,c合成了一個d,這其實是生成樹計數,利用那個生成樹計數的矩陣,輕松搞定,這道題如果它說相同的邊可能有50個,那,我這個方法就是必須的了,嘿嘿。
//#pragma comment(linker, "/STACK:65536000")
#include <map>
#include <stack>
#include <queue>
#include <math.h>
#include <vector>
#include <string>
#include <fstream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#define N 105
#define M 1005
#define E
#define inf 0x3f3f3f3f
#define dinf 1e10
#define linf (LL)1<<60
#define LL long long
#define clr(a,b) memset(a,b,sizeof(a))
using namespace std;

const int mod = 31011;
struct Edge
{
    int a,b,c;
    bool operator<(const Edge & t)const
    {
        return c<t.c;
    }
}edge[M];
int n,m,ans;
int fa[N],ka[N],vis[N];//fa,ka都是並查集,ka是每組邊臨時使用
LL gk[N][N],tmp[N][N];//gk頂點之間的關系,tmp為生成樹計數用的矩陣
vector<int>gra[N];

int findfa(int a,int b[]){return a==b[a]?a:b[a]=findfa(b[a],b);}

LL det(LL a[][N],int n)//生成樹計數
{
    for(int i=0;i<n;i++)for(int j=0;j<n;j++)a[i][j]%=mod;
    int ret=1;
    for(int i=1;i<n;i++)
    {
        for(int j=i+1;j<n;j++)
            while(a[j][i])
            {
                LL t=a[i][i]/a[j][i];
                for(int k=i;k<n;k++)
                    a[i][k]=(a[i][k]-a[j][k]*t)%mod;
                for(int k=i;k<n;k++)
                    swap(a[i][k],a[j][k]);
                ret=-ret;
            }
        if(a[i][i]==0)return 0;
        ret=ret*a[i][i]%mod;
    }
    return (ret+mod)%mod;
}

int main()
{
    //freopen("/home/axorb/in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++)
        scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].c);
    sort(edge,edge+m);
    for(int i=1;i<=n;i++)fa[i]=i,vis[i]=0;
    int pre=-1;ans=1;
    for(int h=0;h<=m;h++)
    {
        if(edge[h].c!=pre||h==m)//一組邊加完
        {
            for(int i=1;i<=n;i++)
                if(vis[i])
                {
                    int u=findfa(i,ka);
                    gra[u].push_back(i);
                    vis[i]=0;
                }
            for(int i=1;i<=n;i++)//枚舉每個聯通分量
                if(gra[i].size()>1)
                {
                    for(int a=1;a<=n;a++)
                        for(int b=1;b<=n;b++)
                            tmp[a][b]=0;
                    int len=gra[i].size();
                    for(int a=0;a<len;a++)//構建矩陣
                        for(int b=a+1;b<len;b++)
                        {
                            int la=gra[i][a],lb=gra[i][b];
                            tmp[a][b]=(tmp[b][a]-=gk[la][lb]);
                            tmp[a][a]+=gk[la][lb];tmp[b][b]+=gk[la][lb];
                        }
                    int ret=(int)det(tmp,len);
                    ans=(ans*ret)%mod;
                    for(int a=0;a<len;a++)fa[gra[i][a]]=i;
                }
            for(int i=1;i<=n;i++)
            {
                ka[i]=fa[i]=findfa(i,fa);
                gra[i].clear();
            }
            if(h==m)break;
            pre=edge[h].c;
        }
        int a=edge[h].a,b=edge[h].b;
        int pa=findfa(a,fa),pb=findfa(b,fa);
        if(pa==pb)continue;
        vis[pa]=vis[pb]=1;
        ka[findfa(pa,ka)]=findfa(pb,ka);
        gk[pa][pb]++;gk[pb][pa]++;
    }
    int flag=0;
    for(int i=2;i<=n&&!flag;i++)if(ka[i]!=ka[i-1])flag=1;
    printf("%d\n",flag?0:ans);
	return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM