BZOJ1016:[JSOI2008]最小生成树计数(最小生成树,DFS)
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
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1
Sample Output
8
Solution
有两条定理:
1.不同的最小生成树中,每种权值的边出现的个数是确定的。
2.不同的生成树中,某一种权值的边连接完成后,形成的联通块状态是一样的 。
也就是说可以对于权值相同的那些边分别处理,爆搜出所有可能的连边情况,然后乘法原理计数即可。
Code
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #define N (1009) 5 #define MOD (31011) 6 using namespace std; 7 8 struct Edge 9 { 10 int x,y,v; 11 bool operator < (const Edge &a) const{return v<a.v;} 12 }E[N]; 13 struct Node{int l,r;}a[N]; 14 int n,m,k,fa[N],size[N],cnt,ans=1,sum; 15 16 int Find(int x){return x==fa[x]?x:Find(fa[x]);} 17 18 void Dfs(int l,int r,int d,int v) 19 { 20 if (l>r) 21 { 22 if (d==size[v]) sum=(sum+1)%MOD; 23 return; 24 } 25 if (r-l+1+d<size[v]) return; 26 int fx=Find(E[l].x), fy=Find(E[l].y); 27 if (fx!=fy && d<size[v]) 28 { 29 fa[fx]=fy; 30 Dfs(l+1,r,d+1,v); 31 fa[fx]=fx; 32 } 33 Dfs(l+1,r,d,v); 34 } 35 36 int main() 37 { 38 scanf("%d%d",&n,&m); 39 for (int i=1; i<=m; ++i) 40 scanf("%d%d%d",&E[i].x,&E[i].y,&E[i].v); 41 sort(E+1,E+m+1); 42 for (int i=1; i<=n; ++i) fa[i]=i; 43 for (int i=1; i<=m; ++i) 44 { 45 if (E[i].v!=E[i-1].v) a[++k].l=i, a[k-1].r=i-1; 46 int fx=Find(E[i].x), fy=Find(E[i].y); 47 if (fx!=fy) fa[fx]=fy,cnt++,size[k]++; 48 } 49 a[k].r=m; 50 if (cnt!=n-1){puts("0"); return 0;} 51 52 for (int i=1; i<=n; ++i) fa[i]=i; 53 for (int i=1; i<=k; ++i) 54 { 55 if (!size[i]) continue; 56 sum=0; 57 Dfs(a[i].l,a[i].r,0,i); 58 ans=sum*ans%MOD; 59 for(int j=a[i].l;j<=a[i].r;j++) 60 { 61 int fx=Find(E[j].x), fy=Find(E[j].y); 62 if(fx!=fy) fa[fx]=fy; 63 } 64 } 65 printf("%d\n",ans); 66 }