题目大意
求一个加权无向图的最小生成树的个数。1<=n<=100; 1<=m<=1000,具有相同权值的边不会超过10条。
题解
最小生成树有个关键的性质我常常忽略,那便是由构成最小生成树的边的边权从小到大排序后得到的序列是唯一的。这个很好证明,首先显然如果你可以将序列中的一个数字改掉,那么如果改小了原来的生成树就不是最小的,如果改大了新的生成树就不是最小的了。如果你可以将序列中的一个数字a改大delta,另一个数字b改小delta,那么用边权为a和b-delta的边替换掉a和b,最小生成树便变得更小了。
上面的命题说明,不同的最小生成树的差别就在于边权相等的边集内选哪个了。题目说具有相同权值的边不会超过10条,所以我们先总体Kruskal看看对于每个边权都用到了多少条边,随后在一个个边权相等的边集中枚举组合即可。
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; const int MAX_NODE = 110, MAX_EDGE = 1010, P = 31011; int EWCnt[MAX_EDGE], EWIntreeCnt[MAX_EDGE]; long long Ans; struct Node { Node *PrevFather, *Father; }_nodes[MAX_NODE]; int TotNode; struct Edge { Node *From, *To; int Weight; bool operator < (const Edge& a) const { return Weight < a.Weight; } }_edges[MAX_EDGE]; int TotEdge; struct Discretion { private: int OrgData[MAX_EDGE], Rank[MAX_EDGE]; int N; int LowerBound(int k) { int l = 1, r = N; while (l < r) { int mid = (l + r) / 2; if (k <= OrgData[mid]) r = mid; else l = mid + 1; } return l; } public: void Push(int val) { OrgData[++N] = val; } void Init() { sort(OrgData + 1, OrgData + N + 1); OrgData[0] = -1; int curRank = 0; for (int i = 1; i <= N; i++) Rank[i] = OrgData[i] == OrgData[i - 1] ? curRank : ++curRank; } int GetRank(int val) { return Rank[LowerBound(val)]; } }d; void Read() { scanf("%d%d", &TotNode, &TotEdge); for (int i = 1; i <= TotEdge; i++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); _edges[i].From = _nodes + u; _edges[i].To = _nodes + v; _edges[i].Weight = w; } } void Discrete() { for (int i = 1; i <= TotEdge; i++) d.Push(_edges[i].Weight); d.Init(); for (int i = 1; i <= TotEdge; i++) { _edges[i].Weight = d.GetRank(_edges[i].Weight); EWCnt[_edges[i].Weight]++; } } void InitGraph() { for (int i = 1; i <= TotNode; i++) _nodes[i].PrevFather = _nodes[i].Father = _nodes + i; } Node *FindRoot(Node *cur) { return cur->Father == cur ? cur : cur->Father = FindRoot(cur->Father); } bool Join(Edge *e) { Node *root1 = FindRoot(e->From), *root2 = FindRoot(e->To); if (root1 != root2) { root1->Father = root2; return true; } else return false; } void Kruskal(int begin, int end, bool op) { for (int i = begin; i <= end; i++) if (Join(_edges + i)) EWIntreeCnt[_edges[i].Weight] += op; } void Fa_Prev_Cur() { for (int i = 1; i <= TotNode; i++) _nodes[i].Father = _nodes[i].PrevFather; } void Fa_Cur_Prev() { for (int i = 1; i <= TotNode; i++) _nodes[i].PrevFather = _nodes[i].Father; } int DoSth(vector<int>& chosen, int k) { Fa_Prev_Cur(); for (int i = 0; i < chosen.size(); i++) if (!Join(_edges + k + chosen[i] - 1)) return 0; return 1; } int Combination(vector<int>& chosen, int n, int m, int begin, int k) { chosen.push_back(begin); m--; int ans = 0; if (n - begin < m); else if (m == 0) ans = DoSth(chosen, k); else for (int i = begin + 1; i <= n; i++) ans += Combination(chosen, n, m, i, k); chosen.pop_back(); return ans; } int Combination(int n, int m, int k) { int ans = 0; static vector<int> chosen; for (int i = 1; i <= n - m + 1; i++) ans += Combination(chosen, n, m, i, k); return ans; } int GetAns() { int cur = 1; Ans = 1; while (cur <= TotEdge) { int curW = _edges[cur].Weight; int cnt = Combination(EWCnt[curW], EWIntreeCnt[curW], cur); Ans = Ans * (cnt + (cnt == 0)) % P; Kruskal(cur, cur + EWCnt[curW] - 1, false); Fa_Cur_Prev(); cur += EWCnt[curW]; } return (int)Ans; } bool NotConnect() { Node *root = FindRoot(_nodes + 1); for (int i = 2; i <= TotNode; i++) if (FindRoot(_nodes + i) != root) return true; return false; } int main() { Read(); Discrete(); sort(_edges + 1, _edges + TotEdge + 1); InitGraph(); Kruskal(1, TotEdge, true); if (NotConnect()) { printf("0\n"); return 0; } InitGraph(); printf("%d\n", GetAns()); return 0; }