题目大意:有$n$个点和$m$条边(最多有$10$条边边权相同),求最小生成树个数
题解:对于所有最小生成树,每种边权的边数是一样的。于是就可以求出每种边权在最小生成树中的个数,枚举这种边的边集,求出对于这个边集可以的解(即没有一条边在同一联通块中),再把每种边的方案数乘起来即可。
卡点:无
C++ Code:
#include <cstdio> #include <algorithm> using namespace std; const long long mod = 31011; struct Edge { int from, to, w; bool operator < (const Edge &a) const {return w < a.w;} } e[1010]; int n, m; struct Set { int f[111]; int find(int x) {return ((x == f[x]) ? x : (f[x] = find(f[x])));} bool operator = (const Set &a) { for (int i = 1; i <= n; i++) f[i] = a.f[i]; } } s1, s2; long long ans = 1; int main() { scanf("%d%d", &n, &m); 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); for (int i = 1; i <= n; i++) s1.f[i] = s2.f[i] = i; for (int i = 1; i <= m; i++) { int same = i, cnt = 0, res = 0; while (same < m && e[same].w == e[same + 1].w) same++; s1 = s2; for (int j = i; j <= same; j++) { int u = s1.find(e[j].from), v = s1.find(e[j].to); if (u != v) { s1.f[u] = v; cnt++; } } for (int j = 0; j < 1 << same - i + 1; j++) { bool flag = false; if (__builtin_popcount(j) == cnt) { flag = true; s1 = s2; for (int k = i; k <= same; k++) { if (j & 1 << k - i) { int u = s1.find(e[k].from), v = s1.find(e[k].to); if (u == v) { flag = false; break; } else s1.f[u] = v; } } } res += flag; } for (int j = i; j <= same; j++) { int u = s2.find(e[j].from), v = s2.find(e[j].to); if (u != v) s2.f[u] = v; } ans = (ans * res) % mod; } int tmp = s2.find(1); for (int i = 2; i <= n; i++) if (s2.find(i) != tmp) { puts("0"); return 0; } printf("%d ", ans); return 0; }