次小生成树的定义
设 G=(V,E,w)是连通的无向图,T 是图G 的一个最小生成树。如果有另一棵树T1,满
足不存在树T’,ω(T’)<ω(T1) ,则称T1是图G的次小生成树。
求解次小生成树的算法
约定:由T 进行一次可行交换得到的新的生成树所组成的集合,称为树T的邻集,记为N(T)。
定理 3:设T是图G的最小生成树,如果T1满足ω(T1)=min{ω(T’)| T’∈N(T)},则T1是G
的次小生成树。
证明:如果 T1 不是G 的次小生成树,那么必定存在另一个生成树T’,T’=T 使得
ω(T)≤ω(T’)<ω(T1),由T1的定义式知T不属于N(T),则
E(T’)/E(T)={a1,a2
1,……,at},E(T)/E(T’)={b1,b2,……,bt},其中t≥2。根据引理1 知,存在一
个排列bi1,bi2,……,bit,使得T+aj-bij仍然是G 的生成树,且均属于N(T),所以ω(aj)≥ω(bij),
所以ω(T’)≥ω(T+aj-bij)≥ω(T1),故矛盾。所以T1是图G 的次小生成树。
通过上述定理,我们就有了解决次小生成树问题的基本思路。
首先先求该图的最小生成树T。时间复杂度O(Vlog2V+E)
然后,求T的邻集中权值和最小的生成树,即图G 的次小生成树。
如果只是简单的枚举,复杂度很高。首先枚举两条边的复杂度是O(VE),再判断该交换是否
可行的复杂度是O(V),则总的时间复杂度是O(V2E)。这样的算法显得很盲目。经过简单的
分析不难发现,每加入一条不在树上的边,总能形成一个环,只有删去环上的一条边,才能
保证交换后仍然是生成树,而删去边的权值越大,新得到的生成树的权值和越小。我们可以
以此将复杂度降为O(VE)。这已经前进了一大步,但仍不够好。
回顾上一个模型——最小度限制生成树,我们也曾面临过类似的问题,并且最终采用动态规
划的方法避免了重复计算,使得复杂度大大降低。对于本题,我们可以采用类似的思想。首
先做一步预处理,求出树上每两个结点之间的路径上的权值最大的边,然后,枚举图中不在
树上的边,有了刚才的预处理,我们就可以用O(1)的时间得到形成的环上的权值最大的边。
如何预处理呢?因为这是一棵树,所以并不需要什么高深的算法,只要简单的BFS 即可。
预处理所要的时间复杂度为O(V2)。
这样,这一步时间复杂度降为O(V2)。
综上所述,次小生成树的时间复杂度为O(V2)。
结论1
次小生成树可由最小生成树换一条边得到.
证明:
可以证明下面一个强一些的结论:
T是某一棵最小生成树,T0是任一棵异于T的树,通过变换
T0 --> T1 --> T2 --> ... --> Tn (T) 变成最小生成树.
所谓的变换是,每次把Ti中的某条边换成T中的一条边, 而
且树T(i+1)的权小于等于Ti的权.
具体操作是:
step 1. 在Ti中任取一条不在T中的边uv.
step 2. 把边uv去掉,就剩下两个连通分量A和B,
在T中,必有唯一的边u'v' 连结A和B.
step 3. 显然u'v'的权比uv小 (否则,uv就应该在T中).
把u'v'替换uv即得树T(i+1).
特别地:取T0为任一棵次小生成树,T(n-1) 也就是次小生成树且
跟T差一条边. 结论1得证.
算法:
只要充分利用结论1, 即得V^2的算法. 具体如下:
step 1. 先用prim求出最小生成树T.
在prim的同时,用一个矩阵max[u][v] 记录 在T中连结任意两点u,v的唯一的
路中权值最大的那条边的权值. (注意这里).
这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点
集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边.
step 1 用时 O(V^2).
step 2. 枚举所有不在T中的边uv, 加入边uv则必然替换权为max[u][v]的边.
故总时间为O(V^2).
针对pku1679 的一些网上结论
这题可以先做一次MST, 然后n-1次循环, 每次删树上的一条边, 再做最小生成树, 取这n-1次中最小的一个就是次小的了, 在稠密图时复杂度接近O(n^3).
也可以先做一次MST, 在MST上DFS做下预处理, 算出len[i][j], 表示i到j的路径上最长的边, 预处理复杂度O(n^2). 枚举添加每条不在MST上的边(u,v),加上以后一定会形成一个环, 找到环上权值第二大的边(即除了(u,v)以外的权值最大的边), 把它删掉, 计算当前生成树的权值之和. 取所有枚举修改的生成树权值之和的最小值, 就是次小生成树. 枚举的复杂度是O(m), 总复杂度O(n^2)
经过多次测试(我是用Prim做的), 发现PKU的数据是有问题的. 首先, 数据中存在不连通的图, 并且在原图不连通时既不是输出0(测试过输出0就WA), 也不是输出"Not Unique!".
不管数据怎么样, 算法写得严谨点就能过, 什么是严谨呢? 比如说在做Prim时, 主循环里选不在现有MST中的最近的点时要判断此点是否是INF(用普通堆的话就不需要这种判断了, 但是映射二分堆还是需要判断一下). 但是这里如果发现最近的点是INF, 并且还不足n-1条边, 不能直接返回0, 而是要把现有的边的总长度返回才能AC...
以上内容都不是原创是学习资料,大家分享下
这里附上我wa的代码,大家帮忙看看
#include<iostream> #include<vector> #include<queue> using namespace std; const int N=105; const int M=10500; const int INF=999999999; int usedP[N]; int usedE[M]; int maxm[N][N]; int n,m; struct Node { int x,l,flag,y; Node(int a=0,int b=0,int c=0,int d=0):x(a),l(b),flag(c),y(d){} }node[N]; bool operator < (Node a,Node b) { return a.l>b.l; } struct Edge { int a,b,w; }edge[M]; vector<Node>map[N]; int max(int a,int b) { return a>b?a:b; } int min(int a,int b) { return a<b?a:b; } int SMST() { int ans=0; priority_queue<Node>q; memset(usedP,0,sizeof(usedP)); memset(usedE,0,sizeof(usedE)); int i,j; for(i=0;i<N;i++) for(j=0;j<N;j++) { maxm[i][j]=-INF; } int inpoint[N]; usedP[1]=1; inpoint[1]=1; int pre=1; int t=n-1; int minlen; int num=1; while(t--) { minlen=INF; for(i=0;i<map[pre].size();i++) { if(!usedE[map[pre][i].flag]&&!usedP[map[pre][i].x]) { q.push(map[pre][i]); } } if(q.empty())break; int tempx=q.top().x; int templ=q.top().l; int tempf=q.top().flag; int temppre=q.top().y; pre=tempx; //printf("%d/n",tempf); q.pop(); usedE[tempf]=1; usedP[tempx]=1; ans+=templ; for(i=1;i<=num;i++) { //printf("%d %d %d/n",templ,tempx,inpoint[i]); maxm[inpoint[i]][tempx]=maxm[tempx][inpoint[i]] =max(maxm[inpoint[i]][temppre],templ); } inpoint[++num]=tempx; } return ans; } int main() { int cas; while(scanf("%d",&cas)!=EOF) { while(cas--) { scanf("%d %d",&n,&m); int x,y,len,i; for(i=1;i<=m;i++) { scanf("%d %d %d",&x,&y,&len); edge[i].a=x,edge[i].b=y,edge[i].w=len; map[x].push_back(Node(y,len,i,x)); map[y].push_back(Node(x,len,i,y)); } int ans=SMST(); int ans2=INF; for(i=1;i<=n;i++) for(int j=1;j<=n;j++) { //printf("%d %d %d/n",i,j,maxm[i][j]); } for(i=1;i<=m;i++) { if(!usedE[i]) { //printf("%d %d %d/n",i,edge[i].w,maxm[edge[i].a][edge[i].b]); ans2=min(ans2,ans+edge[i].w-maxm[edge[i].a][edge[i].b]); } } if(ans2==ans) printf("Not Unique!/n"); else printf("%d/n",ans); for(i=0;i<N;i++) map[i].clear(); } } return 0; }