【JSOI2008】最小生成树计数

【JSOI2008】最小生成树计数

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
http://mail.bashu.cn:8080/BSoiOnline/showproblem?problem_id=2388
___________________________________________________________________________________________
网上搜到一个定理:对于一个图的任意一个最小生成树,它取边权为Ki的边取了Ni条,并且这Ni条边连接的点集为Si,那么对于它所有的最小生成树,边权为Ki的边一定会取Ni条,并且这Ni条边连接的点集为Si。
实现的过程中发现这个定理有点问题,具体看代码注释。

注意如果有一种长度的边没有用的话,要跳过TOTAL,不然ANS就会为0
1 /*
2 网上搜到的定理:(经过修改)
3 对于一个图的任意一个最小生成树,将其边从小到大排序。
4 如果它取边权为Ki的边取了Ni条,并且N1到Ni中所有的边连接形成的联通块为S,那么对于它所有的最小生成树,边权为Ki的边一定会取Ni条,并且N1到Ni中所有的边连接形成的联通块仍为S。
5
6 算法实现:
7 大体框架就是KRUSKAL算法。两个并查集,一个是KRUSKAL用的,一个是DFS用的。
8 找边的同时进行长度相同的边归类存储。
9 记录长度为L[I]边用了多少个,并且记录当前已经添加到森林里的点。
10 对长度为L[I]的边进行DFS,当边的个数达到要求且当前所有用到的边(即长度<=L[I]的所有边)没有成环(并查集实现)则TOTAL+1
11 对一组长度的边DFS完后要更新并查集2,记录新的联通块。
12 ANS=TOTAL1*TOTAL2*......
13 */
14 #include<stdio.h>
15 #include<memory.h>
16 #include<stdlib.h>
17  #define MOD 31011
18  #define N 105
19  #define M 1005
20  int edge_sum[N], //记录长度相同的第I组边的总数
21   edge_st[N], //记录长度相同的第I组边在EDGE数组中的起始位置
22   f[N], //Kruskal主程序并查集
23   f_2[N]; //Dfs 时用的并查集
24  bool vis[N]; //记录已经添加到森林里的点
25  int n, m, i, top;
26 long long total, ans;
27 struct node
28 {
29 int u, v, l;
30 } edge[M];
31 int cmp(const void *a, const void *b)
32 {
33 return (*(node *) a).l - (*(node *) b).l;
34 }
35 int getf(int u)
36 {
37 int temp;
38 temp = f[u];
39 if (temp != u)
40 temp = getf(temp);
41 f[u] = temp;
42 return temp;
43 }
44 int getf_2(int u)
45 {
46 if (f_2[u] != u)
47 return getf_2(f_2[u]);
48 else
49 return u;
50 }
51 void merge(int u, int v)
52 {
53 int a, b;
54 a = getf(u);
55 b = getf(v);
56 f[a] = b;
57 }
58 void dfs(int l, int r, int sum)
59 {
60 int fa, fb;
61 if (sum == 0)
62 {
63 total++;
64 return;
65 }
66 if (l > r)
67 return;
68 fa = getf_2(edge[l].u);
69 fb = getf_2(edge[l].v);
70 if (vis[edge[l].u] && vis[edge[l].v] && fa != fb)
71 {
72 f_2[fa] = fb;
73 dfs(l + 1, r, sum - 1);
74 f_2[fa] = fa;
75 }
76 dfs(l + 1, r, sum);
77 }
78 void search(int top)
79 {
80 total = 0;
81 dfs(edge_st[top], edge_st[top + 1] - 1, edge_sum[top]);
82 ans = (ans * total) % MOD;
83 }
84 bool Kruskal()
85 {
86 int i, j, sum;
87 for (i = 1; i <= n; i++)
88 {
89 f[i] = i;
90 f_2[i] = i;
91 }
92 top = 0;
93 sum = 0;
94 memset(vis, 0, sizeof(vis));
95 for (i = 0; i < m; i++)
96 {
97 if (i == 0 || edge[i].l != edge[i - 1].l) //记录边长相同的边
98 {
99 top++;
100 edge_st[top] = i;
101 edge_sum[top] = 0;
102 if (i > 0)
103 {
104 search(top - 1); //对上一组边长相同的边进行DFS
105 for (j = 1; j <= n; j++)//更新并查集
106 f_2[j] = f[j];
107
108 }
109 }
110 if (getf(edge[i].u) != getf(edge[i].v))
111 {
112 merge(edge[i].u, edge[i].v);
113 edge_sum[top]++;
114 vis[edge[i].u] = true;
115 vis[edge[i].v] = true;
116 sum++;
117 }
118 if (sum == n - 1)
119 break;
120 }
121 for (i = i + 1; i <= m; i++)
122 if (edge[i].l != edge[i - 1].l)
123 {
124 edge_st[top + 1] = i;
125 break;
126 }
127 search(top);
128 if (sum == n - 1)
129 return true;
130 return false;
131 }
132 int main()
133 {
134 while (scanf("%d%d", &n, &m) != EOF)
135 {
136 for (i = 0; i < m; i++)
137 scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].l);
138 qsort(edge, m, sizeof(edge[0]), cmp);
139 ans = 1;
140 if (!Kruskal())
141 printf("0\n");
142 else
143 printf("%lld\n", ans);
144 }
145 return 0;
146 }

posted on 2011-03-10 15:55  风也轻云也淡  阅读(1104)  评论(0编辑  收藏  举报