题目大意:
就是给出一个图求最小生成树的个数
大致思路:
表示写这个题的时候坑了一段时间....
首先根据网上的众多题解, 通过Kruskal的边的阶段性将整个过程分为多次求缩点后形成连通分量的过程, 那么对于每个阶段就是几个生成树的方案的乘积, 然后将新产生的连通分量缩点, 一直进行到Kruskal结束
对于每一阶段就是一个子图上的生成树个数的问题
这个地方需要注意在计算Kirchhoff矩阵的时候, 用到的邻接矩阵中A[i][j]应该是点vi和vj之间的边数, 而不是一直是1或者0....
对于每个连通分量在各个阶段的生成树个数用Matrix_Tree定理求解, 而对于连通分量缩点的问题用并查集维护, 注意一下Kruskal和连通分量的并查集更新顺序就行了
代码如下:
Result : Accepted Memory : 1940 KB Time : 78 ms
/* * Author: Gatevin * Created Time: 2015/8/29 23:43:44 * File Name: Iki_Hiyori.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; /* * Matrix-Tree定理(Kirchhoff矩阵-树定理) * 图G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n - 1阶主子式的行列式的绝对值 * 相关概念: * 定义一个如G的Kirchhoff矩阵C[G]为G的度数矩阵D[G]与邻接矩阵A[G]的差 * 即C[G] = D[G] - A[G] * G的度数矩阵D[G]: 是一个n*n的矩阵, 满足当i != j 时d[ij] = 0, 当i == j时d[ij] = (v[i]的度数) * G的邻接矩阵A[G]: 是一个n*n的矩阵, 满足如果v[i]与v[j]之间有边相连则a[ij] = 1否则a[ij] = 0, 其实应该是v[i]和v[j]之间相连的边数 * n - 1阶主子式: 即矩阵去掉第i行和i列的所有元素之后得到的矩阵(1 <= i <= n) */ #define maxn 110 #define maxm 1010 lint mod, n, m; vector<int> gra[maxn]; struct Edges { int u, v, w; Edges(int _u, int _v, int _w) : u(_u), v(_v), w(_w){} Edges(){} }; bool operator < (const Edges &e1, const Edges &e2) { return e1.w < e2.w; } Edges edge[maxm]; struct Matrix { lint a[maxn][maxn]; Matrix() { for(int i = 0; i < maxn; i++) for(int j = 0; j < maxn; j++) a[i][j] = (i == j); } lint* operator[](int x) { return a[x]; } lint det(int n) { for(int i = 1; i < n; i++) for(int j = 1; j < n; j++) a[i][j] %= mod; lint ret = 1; for(int i = 1; i < n; i++) { for(int j = i + 1; j < n; j++) while(a[j][i]) { lint tmp = a[i][i] / a[j][i]; for(int k = i; k < n; k++) a[i][k] = (a[i][k] - a[j][k]*tmp) % 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; } }; Matrix operator - (const Matrix &m1, const Matrix &m2) { Matrix ret; for(int i = 0; i < maxn; i++) for(int j = 0; j < maxn; j++) ret[i][j] = m1.a[i][j] - m2.a[i][j]; return ret; } Matrix K, A; bitset<maxn> vis;//记录Kruskal每一阶段的图新的要处理的连通分量的代表结点(缩点处理之后的) int fa[maxn], ka[maxn];//两个并查集, 一个用在Kruskal, 一个维护连通分量 int find(int x, int *f) { return x == f[x] ? x : f[x] = find(f[x], f); } lint matrixTree() { for(int i = 1; i <= n; i++) if(vis[i])//找出连通分量 { gra[find(i, ka)].push_back(i);//这里ka能反映出新图的连通关系, 而fa还没更新 vis[i] = 0; } lint ret = 1; for(int i = 1; i <= n; i++) if(gra[i].size() > 1)//枚举计算各个连通分量造成的不同生成树数量 { memset(K.a, 0, sizeof(K.a)); int sz = gra[i].size(); for(int x = 0; x < sz; x++) { for(int y = x + 1; y < sz; y++)//每个连通分量求Kirchhoff矩阵 { int u = gra[i][x], v = gra[i][y]; K[x + 1][y + 1] = 0 - A[u][v]; K[y + 1][x + 1] = K[x + 1][y + 1]; K[x + 1][x + 1] += A[u][v] - 0; K[y + 1][y + 1] += A[v][u] - 0; } } ret = ret*K.det(sz) % mod; for(int j = 0; j < sz; j++) fa[gra[i][j]] = i;//更新并查集 } for(int i = 1; i <= n; i++)//连通图缩点, 将连通分量并查集的根节点变为一致 { fa[i] = find(i, fa); ka[i] = fa[i]; gra[i].clear(); } return ret; } void solve() { while(scanf("%d %d %d", &n, &m, &mod), n || m || mod) { for(int i = 0; i < m; i++) scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].w); sort(edge, edge + m); for(int i = 1; i <= n; i++) fa[i] = ka[i] = i; vis.reset(); memset(A.a, 0, sizeof(A.a));//邻接矩阵 lint ans = 1; int pre = edge[0].w; for(int i = 0; i < m; i++) { int fu = find(edge[i].u, fa), fv = find(edge[i].v, fa); if(fu != fv) { ka[find(fu, ka)] = find(fv, ka);//只更新连通分量用的并查集 vis[fu] = 1; vis[fv] = 1; A[fu][fv]++; A[fv][fu]++; //注意这里不更新Kruskal的并查集, 在这一阶段结束才更新, 这是为了使得邻接矩阵代表出连通分量之间的关系 } if(i == m - 1 || edge[i + 1].w != pre) { ans = ans*matrixTree() % mod; pre = edge[i + 1].w; } } for(int i = 2; i <= n; i++) if(ka[i] != ka[i - 1])//图不连通 { ans = 0; break; } printf("%I64d\n", ans % mod); } } int main() { solve(); return 0; }