P1111 修复公路
题目背景
A地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出A地区的村庄数N,和公路数M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)
输入输出格式
输入格式:第1行两个正整数N,M
下面M行,每行3个正整数x, y, t,告诉你这条公路连着x,y两个村庄,在时间t时能修复完成这条公路。
输出格式:如果全部公路修复完毕仍然存在两个村庄无法通车,则输出-1,否则输出最早什么时候任意两个村庄能够通车。
输入输出样例
说明
N<=1000,M<=100000
x<=N,y<=N,t<=100000
=========================================
前面分析有点乱,重点还是关注黑体部分以及后面的总结:
拿到题目,明显最小生成树,但应该用哪种解法,刚开始考虑Prim算法,但发现很多问题:
补充:该题所有道路可同时修复,所以要输出最小生成树中最大的那条边;
①、Prim算法的主要思想就是求解出每个点到最近白点的距离,然后根据这些最短距离连接起所有白点;
②、Pirm局限性:无法判断该图是否连通;判断图的连通性,我们可以想到如果最后去重后得到的边数>=n-1,则该图可构成最小生成树,而对于Prim算法,若不连通的图中有一部分形成环,则该条件不再试用。而对于Kruskal来说,见下面......
③、如果硬要做的话(无法摆脱Prim 的局限性):该题有不连通的两点,而且两点间可能有多条线路,所以对于图的构建来说,要有g[i][j]=g[j][i]=min(多条路中最短的一条,去掉耗时长的路),且g初始化为一个极大值。
而对于Kruskal来说,其核心思想是判断当前遍历的一条边是否可以加入到最小生成树中,能加入最小生成树的边,所连接的都是两个不连通的区域,即加入边的过程也是打通两个不连通区域的过程,所以如果生成树中的边数k能够到达n-1,则说明该图可连通(当前边可连接起n个孤立区域\点),如果到达不了n-1,说明该图不能连通。
另外在判断是否可入树时,我们要考虑该边所连两点是否有相同祖先,若没有,则需要合并两点(合并后具有共同祖先),这样也就能保证连接该两点的其他边不会再进入最小生成树中。
所以Prim的90分代码:
#include<bits/stdc++.h> using namespace std; int g[1001][1001]; bool flag[1001][1001]; bool u[1001]; int minn[1001]; int main() { //freopen("1.txt","r",stdin); memset(g,0x7f,sizeof(g));//important; memset(minn,0x7f,sizeof(minn)); memset(u,1,sizeof(u)); int n,m,tm; cin>>n>>m; tm=m;//wrong if(tm<n-1) { cout<<"-1"<<endl; return 0; } for(int i=1;i<=m;++i) { int x,y,z; cin>>x>>y>>z; if(flag[x][y]==0||flag[y][x]==0) { flag[x][y]=1; flag[y][x]=1; g[x][y]=z;//wrong g[y][x]=z; // cout<<x<<" "<<y<<endl; } else { tm--; if(tm<n-1) { cout<<"-1"<<endl; return 0; } if(z<g[x][y])g[x][y]=z,g[y][x]=z; } } m=tm; //cout<<g[2][3]<<endl; //cout<<m<<endl; minn[1]=0; for(int i=1;i<=n;++i) { int k=0; for(int j=1;j<=n;++j) if(u[j]&&minn[j]<minn[k])k=j; u[k]=0; //cout<<"k="<<k<<endl; for(int j=1;j<=n;++j) { if(u[j]&&g[k][j]<minn[j])minn[j]=g[k][j]; } } int ans=0; for(int i=1;i<=n;++i){ if(minn[i]>ans)ans=minn[i];//wrong //cout<<minn[i]<<endl ; } cout<<ans<<endl; return 0; }
Kruskal的AC代码:
#include<bits/stdc++.h> using namespace std; struct point { int l,r,v; }a[1000001]; int fat[1001]; bool cmp(point x,point y) { return x.v<y.v; } int father(int x) { while(x!=fat[x])x=fat[x];//非递归实现 查找 功能; return x; } void unionn(int x,int y) { fat[father(x)]=father(y); } int main() { //freopen("1.txt","r",stdin); int m,n; cin>>n>>m; for(int i=1;i<=m;++i) { int x,y,z; cin>>x>>y>>z; a[i].l=x; a[i].r=y; a[i].v=z; } for(int i=1;i<=n;++i)fat[i]=i; sort(a+1,a+1+m,cmp); int k=0,ans; bool flag=1; for(int i=1;i<=m;++i) { if(father(a[i].l)!=father(a[i].r)) { k++; unionn(a[i].l,a[i].r); } if(k==n-1) { ans=a[i].v; flag=0;//判断图是否可连通; break; } } if(flag)cout<<"-1"<<endl; else cout<<ans<<endl; return 0; }
最后总结:
目前来看,最小生成树类题目的输入数据有三种给出形式:
1、一种是给出图中所有点的指向(有向图);(例如信息学P511页最优布线问题)
注意点:
Prim算法中要将 邻接矩阵 初始化为极大值,以防止出现有不相连点的情况,同时注意不能构成最小生成树的情况(所以一般不用Prim吧);
Kruskal:无注意点;每有一条单向边,总边数m+1。
2、另一种是只给出能相连的点之间的关系(和 1 类似);(如本题)
Prim算法中上同......
Kruskal:无注意点。
3、还有一种是给出n个孤立点(任意一点都能与该点外的任意一点相连)。
(如 洛谷 公路修建:https://www.luogu.org/problemnew/show/P1265)
Prim中初不初始化无所谓。
Kruskal:无注意点。
(重点)综上所述:
边数较多,用Prim,但需保证所给图能够生成最小生
成树(一般来说比如题目给出好多孤立点);边数较少,用
Kruscal,“一边一m++”,其他暂无注意点。
The end;