修路方案
时间限制:
3000 ms | 内存限制:
65535 KB
难度:
5
- 描述
-
南将军率领着许多部队,它们分别驻扎在N个不同的城市里,这些城市分别编号1~N,由于交通不太便利,南将军准备修路。
现在已经知道哪些城市之间可以修路,如果修路,花费是多少。
现在,军师小工已经找到了一种修路的方案,能够使各个城市都联通起来,而且花费最少。
但是,南将军说,这个修路方案所拼成的图案很不吉利,想让小工计算一下是否存在另外一种方案花费和刚才的方案一样,现在你来帮小工写一个程序算一下吧。
- 输入
-
第一行输入一个整数T(1<T<20),表示测试数据的组数
每组测试数据的第一行是两个整数V,E,(3<V<500,10<E<200000)分别表示城市的个数和城市之间路的条数。数据保证所有的城市都有路相连。
随后的E行,每行有三个数字A B L,表示A号城市与B号城市之间修路花费为L。 - 输出
- 对于每组测试数据输出Yes或No(如果存在两种以上的最小花费方案则输出Yes,如果最小花费的方案只有一种,则输出No)
- 样例输入
-
2 3 3 1 2 1 2 3 2 3 1 3 4 4 1 2 2 2 3 2 3 4 2 4 1 2
- 样例输出
-
No
Yes
-
-
题目分析求次小生成树一般有两种办法:
-
a.利用克鲁斯卡尔先生成一次最小生成树并保存每条树上的边的下标,然后遍历这些树上的边,然后在把每次树上的边执行删除-->重新建树-->更新最小权值-->恢复的过程,每次删除树一条边后,就破坏了原来的树,重新建成的树一定不是原来的树,重新建成的树的最小权值就是次小的,注意每次删边的时候如果删掉的是桥从而导致图的不联通,所以在克鲁斯卡尔中需要利用并查集统计图中的联通分量的个数,如果整个图不联通返回无穷大,使权值本次更新一定失败。
-
复杂度O(n-1*优化后的克鲁斯卡尔的复杂度)
-
-
#include<iostream>//导入输入输出流头文件 #include<algorithm>//导入头文件 using namespace std;//导入标准命名空间 typedef struct edge//定义边的结构体 { int v,w,d;//定义一条边的两个端点及其边长 bool is_delete;//定义是否被删除标志 } Edge; #define MAXN 200007//定义最大边长 #define MAXNUM 0x3f3f3f3f//定义无穷大值 Edge bian[MAXN];//建立边的数组 int pre[MAXN];//定义集合父节点数组 int n,e;//定义边的数目和点的数目 int MST_id[MAXN];//定义树的下标保存数组 int mid_i;//生成树数组的下标 int cnt;//定义图内连同分量的个数 bool cmp(Edge a,Edge b) { return a.d<b.d; } int Find(int x)//压缩路径 { int temp,p=x; while(x!=pre[x]) x=pre[x];//先找到x所属集合的根节点 while(p!=x)//更新路径上的根节点全部压缩 { temp=pre[p];//从x到根节点路径遍历 pre[p]=x; p=temp; } return x;//返回该集合的根节点 } int kruskal(int p)//进入克鲁斯卡尔算法 { int sum=0;//定义最小生成树权值和sum for(int i=0;i<e;i++)//遍历每条边 { int p1=Find(bian[i].v); int p2=Find(bian[i].w); if(bian[i].is_delete==false&&p1!=p2)//如果这条边没有被标记删除,并且两个点集不一个父节点 { cnt--;//集合数目减一个 pre[p1]=pre[p2];//父节点改变 sum+=bian[i].d;//权值和加这条边的权值 if(p==1)//如果是第一次生成 { MST_id[mid_i++]=i;//记录最小生成树的边的id } if(cnt==1) break; } } if(cnt==1)//如果最后全图联通返回权值和 return sum; else//否则返回无穷大 return MAXN; } void init()//初始化父节点和集合个数cnt { for(int i=0;i<=n;i++) pre[i]=i; cnt=n; } int main() { ios::sync_with_stdio(false);//优化输入输出 int ncase;//定义测试组数 cin>>ncase; while(ncase--) { cin>>n>>e;//给定顶点数和边数 mid_i=0; init();//初始化 for(int i=0; i<e; i++) { int v,w,d; cin>>v>>w>>d; bian[i].d=d; bian[i].v=v; bian[i].w=w;//建图 bian[i].is_delete=false; } sort(bian,bian+e,cmp);//排序 int sum=kruskal(1);//获得最小权值并标记最小生成树的边 int tsum=MAXNUM; for(int i=0;i<mid_i;i++) { //cout<<mid_i<<endl; init();//每次初始化 bian[MST_id[i]].is_delete=true;//把最小生成树的一条边删除 tsum=min(kruskal(0),tsum);//更新最小值 bian[MST_id[i]].is_delete=false;//恢复这条边 } if(tsum==sum)//如果数目大于0,就存在 { cout<<"Yes"<<endl; } else cout<<"No"<<endl; } }
第二种是借助普里找不在树中最大边看看原图中有没有与之等价的,有就说明不唯一
O(n^2)
#include<iostream>//导入输入输出流函数 #include<string.h>//用到memset using namespace std;//导入标准命名空间 #define MAXN 505//定义最大的定点数 #define MAXNUM 0x3f3f3f3f//定义无穷大 int vis[MAXN];//访问标记数组 int graph[MAXN][MAXN];//矩阵图 int n,e;//顶点数,边数 int low[MAXN];//保存已经建好的树的顶点到各个点的最小值 int MST[MAXN][MAXN];//i到j路径上的最小值 int is_MST[MAXN][MAXN];//是否在树中 int pre[MAXN];//确定的那一点的边上的另一个节点 int csum=MAXNUM; int sum=0; void init() { memset(graph,0x3f,sizeof(graph));//初始化图 for(int i=0;i<=n;i++) graph[i][i]=0;//将自己和自己设置成0 memset(MST,0,sizeof(MST));//初始化为0 memset(is_MST,0,sizeof(is_MST));//初始化为0 csum=MAXNUM; sum=0; } bool prim(int s)//prim算法 { for(int i=1;i<=n;i++) { low[i]=graph[s][i];//每个点到s的距离 pre[i]=s;//每个点的直接前驱是s vis[i]=0;//初始化每个点都没有被访问过 } vis[s]=1;//把s点设置访问过 for(int j=1;j<n;j++)//循环n-1次 { int tmin=MAXN;// int fa=0;// int ti=-1; for(int i=1;i<=n;i++) { if(vis[i]==0&&low[i]<tmin) { tmin=low[i]; ti=i; } }//找到最小的low以及下标 sum+=tmin; vis[ti]=1;//设置为已经找到 fa=pre[ti];//取出ti这个点所在边的另一个点 is_MST[ti][fa]=is_MST[fa][ti]=1;//将这条边标记为在树中 for(int i=1;i<=n;i++) { if(vis[i]==1&&i!=ti) { MST[i][ti]=MST[ti][i]=max( MST[i][fa],low[ti]);//更新最大的从其他已经在树中点到刚找的点的路径上的最大边 } if(!vis[i]&&low[i]>graph[ti][i])//如果没有被访问过,s松弛low { low[i]=graph[ti][i]; pre[i]=ti; } } } for(int i=1;i<=n;i++) { for(int j=1;j<i;j++) { if(is_MST[i][j]==0&&MST[i][j]!=MAXNUM)//如果有边并且没在树里面 { //res=min(res,mst-path[i][j]+g[i][j]); 求具体的csum csum=min(csum,sum-MST[i][j]+graph[i][j]);//求次小生成树 if(graph[i][j]==MST[i][j])//如果在原图中有等价的最大边则一定存在 { //return true; } } } } return false; } int main() { ios::sync_with_stdio(false); int ncase; cin>>ncase; // ios::sync_with_stdio(false); while(ncase--) { cin>>n>>e; init(); for(int i=0;i<e;i++) { int v,w,d; cin>>v>>w>>d; graph[v][w]=d; graph[w][v]=d; // cout<<i<<endl; }//建图 prim(1); if(sum==csum)//如果原来的和新建的一样,那么就输出yes { cout<<"Yes"<<endl; } else { cout<<"No"<<endl; } } return 0; }
还是那句话点少用普里母
点多用克鲁斯卡尔