在图论中,树:任意两个顶点间有且只有一条路径的图。
生成树:包含了图中所有顶点的一种树。
最小生成树:对于连通的带权图(连通网)G,其生成树也是带权的。生成树T各边的权值总和称为该树的权,权最小的生成树称为G的最小生成树(Minimum Spanning Tree)。最小生成树可简记为MST。
但是,对于一个图而言,最小生成树并不是唯一的。
现在,给你一个连通的有权无向图,图中不包含有自环和重边,你的任务就是寻找出有多少条边,它至少在一个最小生成树里。图保证连通。
输入数据第一行包含一个整数T,表示测试数据的组数。对于每组测试数据:
第一行包含两个整数n,m(1<n<100000,n-1<m<100000),接下来m行,每行三个整数a,b,v(1<=a,b<=n,1<v<500),表示第i条路线连接景点A和景点B,距离是V。两个数字之间用空格隔开。
思路:用kruskal算法模拟生成树的过程。同时也是一个贪心生成树的过程,我们知道,生成的树的边权值和是一定的,那么对于边的替换的值也是能够确定的:只有权值相同的边才有可能是另一种生成树方法的边。
然后我就呆萌的记录有多少重边权值的边,然后加上n-1,开开心心的提交,实力WA。一组数据就可以干掉我:
3 3
1 2 1
1 2 2
2 3 1
所以记得一定不要跟我犯一样的错误,我们需要的是动态判断一条边权值相同的边能否可能是另一种生成树方法的边。我们直接在kruskal算法过程中加上动态判断的成分就可以了,那么要如何判断呢?遍历每一条边的时候,如果有相同权值的边,像kruskal一样的判断条件,判断这条边能否加入生成树中即可。
kruskal算法判断一条边是否能够贪心的加入生成树中:
for(int i=0;i<m;i++) { if(find(a[i].x)!=find(a[i].y)) { zhongquanzhi+=a[i].w; merge(a[i].x,a[i].y); } }
for(int i=0;i<m;i=j) { for(j=i;a[i].w==a[j].w;j++) { if(find(a[j].x)!=find(a[j].y)) { output++; } } for(j=i;a[i].w==a[j].w;j++) { if(find(a[j].x)!=find(a[j].y)) { merge(a[j].x,a[j].y); } } }
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int f[1000050]; struct path { int x,y,w; }a[100050]; int cmp(path a,path b) { return a.w<b.w; } int find(int x) { return f[x] == x ? x : (f[x] = find(f[x])); } void merge(int a,int b) { int A,B; A=find(a); B=find(b); if(A!=B) f[B]=A; } int main() { int t; scanf("%d",&t); while(t--) { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { f[i]=i; } for(int i=0;i<m;i++) { scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w); } sort(a,a+m,cmp); int output=0; int j; for(int i=0;i<m;i=j) { for(j=i;a[i].w==a[j].w;j++) { if(find(a[j].x)!=find(a[j].y)) { output++; } } for(j=i;a[i].w==a[j].w;j++) { if(find(a[j].x)!=find(a[j].y)) { merge(a[j].x,a[j].y); } } } printf("%d\n",output); } }