Description
现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。
Input
第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10 条。
Output
输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。
Sample Input
4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1
Sample Output
8
HINT
Source
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 31011
using namespace std;
int n,m,cnt,tot,ans=1,sum;
int fa[105];
struct edge{int x,y,v;}e[1005];
struct data{int l,r,v;}a[1005];
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0'||ch>'9'){ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x;
}
bool cmp(edge a,edge b){return a.v<b.v;}
int find(int x){return x==fa[x]?x:find(fa[x]);}
void dfs(int x,int now,int k)
{
if(now==a[x].r+1)
{
if(k==a[x].v)sum++;
return;
}
int p=find(e[now].x),q=find(e[now].y);
if(p!=q)
{
fa[p]=q;
dfs(x,now+1,k+1);
fa[p]=p;fa[q]=q;
}
dfs(x,now+1,k);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
e[i].x=read(),e[i].y=read(),e[i].v=read();
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++)
{
if(e[i].v!=e[i-1].v){a[++cnt].l=i;a[cnt-1].r=i-1;}
int p=find(e[i].x),q=find(e[i].y);
if(p!=q){fa[p]=q;a[cnt].v++;tot++;}
}
a[cnt].r=m;
if(tot!=n-1){printf("0");return 0;}
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=cnt;i++)
{
sum=0;
dfs(i,a[i].l,0);
ans=(ans*sum)%mod;
for(int j=a[i].l;j<=a[i].r;j++)
{
int p=find(e[j].x),q=find(e[j].y);
if(p!=q)fa[p]=q;
}
}
printf("%d",ans);
return 0;
}
该算法用2^N次方来搜索sum值,当相同权值的边过多时,很有可能超时
还有一种算法能有效的避免该问题
O(N^3)
/*
*题目大意:
*给出一个简单无向加权图,求这个图中有多少个不同的最小生成树;
*由于不同的最小生成树可能很多,所以只需输出方案数对31011的模就可以了;
*
*算法思想:
*Kruskal+Matrix_Tree定理;
*
*先按照任意顺序对等长的边进行排序;
*然后利用并查集将所有长度为L0的边的处理当作一个阶段来整体看待;
*可以定义一个数组的vector向量来保存每一个连通块的边的信息;
*即将原图划分成多个连通块,每个连通块里面的边的权值都相同;
*针对每一个连通块构建对应的Kirchhoff矩阵C,利用Matrix_Tree定理求每一个连通块的生成树个数;
*最后把他们的值相乘即可;
*
*Matrix_Tree定理:
*G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n-1阶主子式的行列式的绝对值;
*n-1阶主子式就是对于r(1≤r≤n),将C[G]的第r行,第r列同时去掉后得到的新矩阵,用Cr[G]表示;
**/
/****************************************************************/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 111;
const int M = 1111;
const int mod = 31011;
struct Edges {
int a, b, c;
bool operator<(const Edges & x) const {
return c < x.c;
}
} edge[M];
int n, m;
int f[N], U[N], vist[N]; //f,U都是并查集,U是每组边临时使用
int G[N][N], C[N][N]; //G顶点之间的关系,C为生成树计数用的Kirchhoff矩阵
vector<int> V[N]; //记录每个连通分量
int Find(int x, int f[]) {
if (x == f[x])
return x;
else
return Find(f[x], f);
}
int det(int a[][N], int n) //生成树计数:Matrix-Tree定理
{
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
a[i][j] %= mod;
int ret = 1;
for (int i = 1; i < n; i++) {
for (int j = i + 1; j < n; j++)
while (a[j][i]) {
int t = a[i][i] / a[j][i];
for (int k = i; k < n; k++)
a[i][k] = (a[i][k] - a[j][k] * t) % 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;
}
if (ret < 0)
ret = -ret;
return (ret + mod) % mod;
}
void Solve() {
sort(edge, edge + m); //按权值排序
for (int i = 1; i <= n; i++) //初始化并查集
{
f[i] = i;
vist[i] = 0;
}
int Edge = -1; //记录相同的权值的边
int ans = 1;
for (int k = 0; k <= m; k++) {
if (edge[k].c != Edge || k == m) //一组相等的边,即权值都为Edge的边加完
{
for (int i = 1; i <= n; i++) {
if (vist[i]) {
int u = Find(i, U);
V[u].push_back(i);
vist[i] = 0;
}
}
for (int i = 1; i <= n; i++) //枚举每个连通分量
{
if (V[i].size() > 1) {
for (int a = 1; a <= n; a++)
for (int b = 1; b <= n; b++)
C[a][b] = 0;
int len = V[i].size();
for (int a = 0; a < len; a++) //构建Kirchhoff矩阵C
for (int b = a + 1; b < len; b++) {
int a1 = V[i][a];
int b1 = V[i][b];
C[a][b] = (C[b][a] -= G[a1][b1]);
C[a][a] += G[a1][b1]; //连通分量的度
C[b][b] += G[a1][b1];
}
int ret = (int) det(C, len);
ans = (ans * ret) % mod; //对V中的每一个连通块求生成树个数再相乘
for (int a = 0; a < len; a++)
f[V[i][a]] = i;
}
}
for (int i = 1; i <= n; i++) {
U[i] = f[i] = Find(i, f);
V[i].clear();
}
if (k == m)
break;
Edge = edge[k].c;
}
int a = edge[k].a;
int b = edge[k].b;
int a1 = Find(a, f);
int b1 = Find(b, f);
if (a1 == b1)
continue;
vist[a1] = vist[b1] = 1;
U[Find(a1, U)] = Find(b1, U); //并查集操作
G[a1][b1]++;
G[b1][a1]++;
}
int flag = 0;
for (int i = 2; i <= n && !flag; i++)
if (U[i] != U[i - 1])
flag = 1;
if (m == 0)
flag = 1;
printf("%d\n", flag ? 0 : ans % mod);
}
int main() {
while (~scanf("%d%d", &n, &m)) {
memset(G, 0, sizeof(G));
for (int i = 1; i <= n; i++)
V[i].clear();
for (int i = 0; i < m; i++)
scanf("%d%d%d", &edge[i].a, &edge[i].b, &edge[i].c);
Solve();
}
return 0;
}