一行小字。这篇博客是个人gg以前写的博客,一直想写博客,可是直到gg,都没写几篇博客。我再把这篇博客翻出来的时候,不知已通过了半年仍是一年了。博客园的博客系统作的不错,就是数学公式和一些图片会乱,图片的话手动上传一下还ok。数学公式不影响阅读,你们自行理解就ok嘤嘤~
最小生成树Kurskal算法c++
最近公共祖先LCA!算法
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的全部 n 个结点,而且有保持图连通的次少的边。数组
洛谷P4180 [BJWC2010]严格次小生成树markdown
把全部生成树按照权值之和从小到小排序,求排在第二位的生成树。注意,若是最小生成树不惟一,次小生成树的权值和最小生成树相同。函数
次小生成树不会和最小生成树相同,所以能够枚举最小生成树中不在次小生成树中出现的边。 注意最小生成树只有n-1条边,因此只需枚举n-1次。每次在剩下的边里,求一次最小生成树。学习
Kruskal算法求出最小生成树优化
枚举最小生成树的每一条边,对这条边作标记,再进行一次Kruskal算法,Kruskal算法中,跳过被标记的边,求最小生成树,记录答案。spa
去步骤2中所记录答案的最小值即为次小生成树的边权之和。(可直接在步骤2中进行)调试
$O(M\log M+M)$code
$O(NM)$
$O(N)$
总复杂度$O(NM)$
有没有更快些的解决方法呢?
- T为图G的一棵生成树,对于非树边a和树边b,插入边a而且删除边b的操做记为(+a,-b)
- 若是T+a-b仍然是一棵生成树,称(+a,-b)是一个可行交换
- 由T进行一次可行交换之后获得的新的生成树的集合称为T的临集
显然,可得定理:次小生成树在最小生成树的临集中
- 枚举要加入哪条新边,在最小生成树中加入一条边u-v之后,图上会出现一条回路,若是想保持该图为生成树,需删除一条边,删除的边必须是在最小生成树上u到v的路径上,若是想要删除边后的新的生成树有可能为次小生成树,必需要求删除的边这条路径的最长边。
如图,枚举出一条不在最小生成树上的边E1,若是链接E1,E1两端点为(1,4),若是想链接E1后依然保证新图为生成树,需删除路径(1,4)上一条最小生成树上的边。为了求出次小生成树,咱们选择删除原树上的尽量大的边,即删除边(3,4),链接边E1。
如图为新生成的树。
总结一下,新的算法是这样的
那么枚举每一条边复杂度是$O(M)$,对每一条边用dfs求(u,v)上的最长&次长边,复杂度是$O(N)$,总复杂度是$O(MN)$没有遍低呀~
所以,我没须要对算法进行优化,能够经过dfs$O(N)$来预处理每一个点到其k代父节点的路径上的最长边和次长边,再经过lca倍增的方法在$O(\log N)$复杂度下求出u,v两点之间的最长边和次长边。
对于每个点u,咱们记记录anc[u][k]数组,u是节点编号,k表明其$2^k $代父节点。
记录m1[u][k]数组,其中,u是节点编号,k表明从u节点往上$2k$个点,数组的值是u点到u节点上方$2k $个点间的最长边,可得状态转移方程
$$m1[u][k]=\max(m1[u][k-1],m1[anc[u][k-1]][k-1])$$
记录m2[u][k]数组,u,k含义同m1,数组的值是u点到u节点上方$2^k $个点间的次长边,可得状态转移方程
$$m2[u][k]=\begin{cases} \max(m2[u][k-1],m2[anc[u][k-1][k-1]), & \text{if }m1[u][k-1]\ne m1[anc[u][k-1]][k-1] \ \min(m1[u][k-1],m1[anc[u][k-1][k-1]), & \text{if }m1[u][k-1]= m1[anc[u][k-1]][k-1] \end{cases}$$
通过一次dfs后,咱们便可预处理完成以上内容。
遍历$N$个点,每一个点要进行$\log N$次计算,复杂度为 $O(N\log N)$
对于点u和v,咱们在有预处理的状况下想求其两点之间路径的最长边和次长边,可进行如下操做
用倍增法求LCA的方式将两个点向上跳,跳的同时记录m1和m2的值。
向上跳的作法与倍增法求LCA相同,若是没有了解,请先学习倍增法求LCA,此处不作过多介绍。
具体代码以下:
/*参数ma为要新建的边的权值,由于要求严格次小生成树, 因此在原树中删除的边必须严格小于新建的边的权值, 即lca函数的返回值(也就是在原树中删除的边的权值)需严格小于参数ma。*/ long long findBig(long long u,long long i,long long ma){ if(m1[u][i]!=ma){ return m1[u][i]; } return m2[u][i]; } long long lca(long long u,long long v,long long ma){ if(dep[u]<dep[v]){ swap(u,v); } long long res = -INF; for(int i = 20;i>=0;--i){ if(dep[ancestor[u][i]]<dep[v]){ continue; } res = max(res,findBig(u,i,ma)); u = ancestor[u][i]; } if(u==v){ return res; } for(int i = 20;i>=0;--i){ if(ancestor[u][i]!=ancestor[v][i]){ res = max(res,findBig(u,i,ma)); res = max(res,findBig(v,i,ma)); u = ancestor[u][i]; v = ancestor[v][i]; } } res = max(res,findBig(u,0,ma)); res = max(res,findBig(v,0,ma)); return res; }
总复杂度为:$O(MlogN)$
// // Created by SeverusNg on 2020/7/24. // #include <cstdio> #include <algorithm> #include <cstring> #include <cmath> const long long MAXN = 100005; const long long MAXM = 300005; const long long INF = 0x3f3f3f3ff3f3f3f; using namespace std; struct edge{ long long u,v,w; int next; bool operator<(const edge &e)const{ return w<e.w; } } ed[MAXM*2],pool[MAXM]; int head[MAXN]; int topE; void addE(int u,int v,int w){ ed[++topE].u = u; ed[topE].v = v; ed[topE].w = w; ed[topE].next = head[u]; head[u] = topE; return; } int fa[MAXN]; int find(int u){ if(fa[u]==u){ return fa[u]; } fa[u] = find(fa[u]); return fa[u]; } void merge(int u,int v){ u = find(u); v = find(v); fa[u] = v; return; } bool use[MAXM]; long long N,M; long long mst; void kruskal(){ sort(pool+1,pool+M+1); for(int i = 1;i<=M;++i){ long long u = pool[i].u; long long v = pool[i].v; long long w = pool[i].w; if(find(u)!=find(v)){ mst += w; merge(u,v); addE(u,v,w); addE(v,u,w); use[i]=true; } } return; } int dep[MAXN]; int ancestor[MAXN][25]; long long m1[MAXN][25]; long long m2[MAXN][25]; long long findBig(long long u,long long i,long long ma){ if(m1[u][i]!=ma){ return m1[u][i]; } return m2[u][i]; } void dfs(long long u,long long father,long long w){ ancestor[u][0] = father; dep[u] = dep[father]+1; m1[u][0]=w; m2[u][0]=-INF; for(int i = 1;i<=20;++i){ ancestor[u][i]=ancestor[ancestor[u][i-1]][i-1]; m1[u][i]=max(m1[u][i-1],m1[ancestor[u][i-1]][i-1]); m2[u][i]=max(m2[u][i-1],m2[ancestor[u][i-1]][i-1]); if(m1[u][i-1]!=m1[ancestor[u][i-1]][i-1]){ m2[u][i]=max(m2[u][i],min(m1[u][i-1],m1[ancestor[u][i-1]][i-1])); } } for(int i = head[u];i!=0;i=ed[i].next){ long long u,v,w; u = ed[i].u; v = ed[i].v; w = ed[i].w; if(v==father){ continue; } dfs(v,u,w); } return; } long long lca(long long u,long long v,long long ma){ if(dep[u]<dep[v]){ swap(u,v); } long long res = -INF; for(int i = 20;i>=0;--i){ if(dep[ancestor[u][i]]<dep[v]){ continue; } res = max(res,findBig(u,i,ma)); u = ancestor[u][i]; } if(u==v){ return res; } for(int i = 20;i>=0;--i){ if(ancestor[u][i]!=ancestor[v][i]){ res = max(res,findBig(u,i,ma)); res = max(res,findBig(v,i,ma)); u = ancestor[u][i]; v = ancestor[v][i]; } } res = max(res,findBig(u,0,ma)); res = max(res,findBig(v,0,ma)); return res; } int main(){ scanf("%lld%lld",&N,&M); for(int i = 1;i<=N;++i){ fa[i] = i; } for(int i = 1;i<=M;++i){ scanf("%lld%lld%lld",&pool[i].u,&pool[i].v,&pool[i].w); } kruskal(); dfs(1,0,-INF); long long ans = INF; for(int i = 1;i<=M;++i){ if(use[i]){ continue; } long long u = pool[i].u; long long v = pool[i].v; long long w = pool[i].w; ans = min(ans,mst+w-lca(u,v,w)); } printf("%lld\n",ans); return 0; }
嘤嘤嘤,完结撒花~
啊,从建博客,各类调试,到写完这一篇文章,再加上摸鱼,抬头一看,天已经亮了,搞了一个通宵~
不过看到成果,心里仍是十分高兴的~
若有错误,但愿巨佬您可以在评论区指正,感激涕零~