题目链接:
HDU 4408 Minimum Spanning Tree
题意:
给
n
个点和
数据范围:
分析:
在用
Kruskal
算法求最小生成树时,我们的做法是: 将图
G=V,E
中的所有边按照权值由小到大进行排序,等长的边可以按照任意顺序; 然后从小到大扫描每一条边,将未连通的点连通,权值累加, 最后得到的图
G′
就是图
G
的最小生成树。
将所有权值相同的边看成一个阶段整体处理,这一阶段生成树个数和下一阶段是独立的。
我们将求最小生成树时所用的祖先存进
在用
Matrix−Tree
时,邻接矩阵中
A[i][j]
是
v[i]和v[j]
之间的边数,而不是一直是
0
或
对于加入相同权值
same
后的新图可能会形成多个连通块,这是需要对每个连通块计数。找到每个连通块的祖先,将属于这个祖先的点计算。
- 确定祖先和该连通块的所有点
首先寻找的连通块应含有新加进来的边,否则如果该连通块和未加边的连通块(上一阶段的)完全一样,那就重复计数了。所以在加边时用 vis 数组记录 father 数组中的祖先是否被访问,如果被访问那么将属于这个连通块的所有点看成一个点,添加进新图的连通块,将新图的连通块中的每一个点都存在祖先的数组 vec 中。
- 确定连通块内的点与点是否直接连通和每个点的度数
枚举每个新图的连通块的祖先,在
Matrix−Tree定理
中,图
G
的邻接矩阵
我们对新图的每个连通块求生成树的个数,然后累乘,将所有连通块求完后需要清空
vec
数组。同时还要把
U
数组和
最后还要检查图是否连通了(所有点的公共祖先是否一样)。
时间复杂度不好确定。
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int MAX_M = 1010;
ll mod;
int vis[MAX_N], fa[MAX_N], U[MAX_N], link[MAX_N][MAX_N];
vector<int> vec[MAX_N];
ll C[MAX_N][MAX_N];
struct Edge{
int u, v, w;
Edge () {}
Edge (int _u, int _v, int _w): u(_u), v(_v), w(_w) {}
bool operator < (const Edge& rhs) const {
return w < rhs.w;
}
}edge[MAX_M];
void init(int n)
{
memset(link, 0, sizeof(link));
memset(vis, 0, sizeof(vis));
for(int i = 0; i < n; ++i) {
fa[i] = i;
}
}
inline int find(int x, int arr[])
{
return arr[x] == x ? x : arr[x] = find(arr[x], arr);
}
ll det (ll mat[][MAX_N], int n)
{
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
mat[i][j] = (mat[i][j] % mod + mod) % mod;
}
}
ll res = 1;
int cnt = 0;
for(int i = 0; i < n; ++i) {
for(int j = i + 1; j < n; ++j) {
while(mat[j][i]) {
ll t = mat[i][i] / mat[j][i];
for(int k = i; k < n; ++k) {
mat[i][k] = (mat[i][k] - mat[j][k] * t) % mod;
swap(mat[i][k], mat[j][k]);
}
cnt ++;
}
}
if(mat[i][i] == 0) return 0;
res = res * mat[i][i] % mod;
}
if(cnt & 1) res = -res;
return (res + mod) % mod;
}
ll solve(int n, int m)
{
sort(edge, edge + m);
int same = -1;
ll ans = 1;
for(int i = 0; i <= m; ++i) {
if(edge[i].w != same || i == m) {
for(int j = 0; j < n; ++j) {
if(vis[j]) { //旧图以j为祖先的连通块被访问
int fj = find(j, U); //找到连通块所在连通块
vec[fj].push_back(j);
fa[j] = fj;
vis[j] = 0;
}
}
for(int j = 0; j < n; ++j) {
int size = vec[j].size(); //扫描新图的每一个连通块
if(size <= 1) continue;
memset(C, 0, sizeof(C));
for(int k = 0; k < size; ++k) {
for(int h = k + 1; h < size; ++h) {
int u = vec[j][k];
int v = vec[j][h]; //u和v是j这个连通块内的两个点
C[k][h] -= link[u][v]; //link[u][v]表示u和v的连通边数
C[h][k] = C[k][h];
C[k][k] += link[u][v];
C[h][h] += link[v][u]; //对对角线元素添加连通度(连通的边数)
}
}
ans = ans * det(C, size - 1) % mod;
}
for(int j = 0; j < n; ++j) {
U[j] = fa[j] = find(j, fa);
vec[j].clear();
}
if(i == m) break;
same = edge[i].w;
}
int u = edge[i].u, v = edge[i].v;
int fu = find(u, fa), fv = find(v, fa);
if(fu == fv) continue;
vis[fu] = vis[fv] = 1;
U[find(fv, U)] = find(fu, U);
link[fu][fv]++, link[fv][fu]++;
}
int flag = 1, com = find(0, fa);
for(int i = 1; i < n; ++i) { //检验是否所有点都连通
if(com != find(i, fa)) {
flag = 0;
break;
}
}
if(flag == 0) ans = 0;
return (ans + mod) % mod;
}
int main()
{
int n, m;
while(~scanf("%d%d%lld", &n, &m, &mod) && (n || m || mod)) {
init(n);
for(int i = 0; i < m; ++i) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
edge[i] = Edge(u - 1, v - 1, w);
}
printf("%lld\n", solve(n, m));
}
return 0;
}