http://acm.hdu.edu.cn/showproblem.php?pid=4408
将E中的所有边按照权值由小到大进行排序,然后按照从小到大的顺序去扫描每一条边,将未联通的点联通,权值累加,最后得到的图G’就是图G的最小生成树。将所有权值相同的边看成一个阶段整体处理,这一阶段生成树个数和下一阶段是独立的。
我们将求最小生成树所用的祖先存入father数组,将联通块的祖先存进U数组(相当于联通块缩成点)。用vis[i]=1标记联通块的祖先。用link[i][j]表示两个原本独立的联通块(祖先分别是i和j)的联通分量的度(连通的边数)。
在用Matrix-Tree时,邻接矩阵A[i][j]是vi和vj之间的边数,而不是1或0.
对于加入相同权值same后的新图可能会形成多个联通块,这时需要对每个联通块计数。找到每个联通块的祖先,将属于这个祖先的点计算。
• 确定祖先和该联通块的所有点
首先寻找的联通块应该含有新加进来的边,否则如果该联通块和未加边的联通块(上一阶段)完全一样,那就重复计数了。所以在加边时用vis数组记录father数组中的祖先是否被访问,如果被访问那么将属于这个联通块的所有点看作一个点,添加进新图的联通块,将新图的联通块的每个点都存入祖先的数组vec中。
• 确定联通块内的点与点是否直接联通和每个点的度数
枚举每个新图的联通块的祖先,在Matrix-Tree定理中,图G的邻接矩阵A[G]被定义为:当vi和vj直接相连时A[i][j]=1,否则A[i][j]=0。但是由于这里我们将旧图的联通块缩点了,那么新图的点与点应看成是旧图的联通块与联通块,所以新图的邻接矩阵应是点与点的边数,而不是非1即0得关系(而且link[i][j]=link[j][i])。
我们对新图的每个联通块求生成树的个数,然后累乘,将所有联通块求完后需要清空vec数组。同时还要把U数组和father数组更新,因为这时所有的新联通块和旧图的联通块都要合并成一个新的联通块。
最后还要检查图是否联通了(所有点的祖先都相同)。
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <string>
#include <cmath>
#include <set>
using namespace std;
const int maxn = 100 + 5;
const int maxm = 1000 + 5;
typedef long long ll;
ll p, a[maxn][maxn];
int vis[maxn], fa[maxn], U[maxm], link[maxn][maxn];
vector<int> vec[maxn];
struct Edge {
int u, v, w;
Edge(int u = 0, int v = 0, int w = 0) : u(u), v(v), w(w) {}
bool operator <(const Edge& rhs) const {
return w < rhs.w;
}
} edge[maxm];
void init(int n) {
memset(link, 0, sizeof(link));
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; ++i) fa[i] = i;
}
int find(int x, int *arr) {
return arr[x] == x ? x : arr[x] = find(arr[x], arr);
}
ll det(int n) {
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
a[i][j] = (a[i][j] % p + p) % p;
}
}
ll ret = 1;
int flg = 0;
for(int i = 0; i < n; ++i) {
for(int j = i + 1; j < n; ++j) {
while(a[j][i]) {
ll t = a[i][i] / a[j][i];
for(int k = i; k <= n; ++k)
a[i][k] = (a[i][k] - a[j][k] * t) % p;
for(int k = i; k <= n; ++k)
swap(a[i][k], a[j][k]);
++flg;
}
}
if(a[i][i] == 0) return 0;
ret = ret * a[i][i] % p;
}
if(flg & 1) ret = -ret;
return (ret + p) % p;
}
ll solve(int n, int m) {
sort(edge, edge + m);
int same = -1;
ll ret = 1;
for(int i = 0; i <= m; ++i) {
if(edge[i].w != same || i == m) {
for(int j = 1; j <= n; ++j) {
if(vis[j]) {
int fj = find(j, U);
vec[fj].push_back(j);
fa[j] = fj;
vis[j] = 0;
}
}
for(int j = 1; j <= n; ++j) {
int sz = vec[j].size();
if(sz <= 1) continue;
memset(a, 0, sizeof(a));
for(int k = 0; k < sz; ++k) {
for(int h = k + 1; h < sz; ++h) {
int u = vec[j][k];
int v = vec[j][h];
a[k][h] -= link[u][v];
a[h][k] = a[k][h];
a[k][k] += link[u][v];
a[h][h] += link[v][u];
}
}
ret = ret * det(sz - 1) % p;
}
for(int j = 1; 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 flg = 1, com = find(1, fa);
for(int i = 2; i <= n; ++i) {
if(com != find(i, fa)) {
flg = 0;
break;
}
}
if(!flg) ret = 0;
return (ret + p) % p;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
while(cin >> n >> m >> p) {
if(n == 0 && m == 0 && p == 0) break;
init(n);
for(int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
edge[i] = Edge(u, v, w);
}
cout << solve(n, m) << endl;
}
}