最小生成树构成的过程实际上是做 次操作,每一次合并一个点集,直到图中只剩下一个集合为止 。
要达到的就是让每一次合并的代价之和最小。
那么我们实际上可以贪心地选择边权最小的并且能够合并集合的边(Kruskal算法),这个算法的正确性简单来说可以用反证法来证明,假设我们并没有按照 Kruskal 的流程来选择边,那么显而易见的是,每一次的合并操作一定存在一条边与之对应,而没有遵循 Kruskal 的结果是:至少存在一次操作,我们可以用一条权值更小的边来替代这一次操作所选的边,使得答案变得更优,所以正确性就这样不太严格地得到了证明。
| #include<bits/stdc++.h> |
| #define R register |
| using namespace std; |
| template<typename T>inline void re(T &x) |
| { |
| x=0;int f=1;char c=getchar(); |
| while(!isdigit(c)) |
| { |
| if(c=='-')f=-1; |
| c=getchar(); |
| } |
| while(isdigit(c)) |
| { |
| x=(x<<1)+(x<<3)+(c^48); |
| c=getchar(); |
| } |
| x*=f; |
| } |
| template<typename T>inline void wr(T x) |
| { |
| if(x<0)putchar('-'),x=-x; |
| if(x>9)wr(x/10); |
| putchar(x%10^48); |
| } |
| struct Edge |
| { |
| int u,v,w; |
| }E[1000000]; |
| bool cmp(Edge a,Edge b){return a.w<b.w;} |
| int fa[100000],n,m,u,v,w; |
| inline int get(int x){return fa[x]==x?x:fa[x]=get(fa[x]);} |
| inline void pre() |
| { |
| re(n),re(m); |
| for(R int i=1;i<=m;++i) |
| re(E[i].u),re(E[i].v),re(E[i].w); |
| sort(E+1,E+m+1,cmp); |
| for(register int i=1;i<=n;i++)fa[i]=i; |
| } |
| inline void solve() |
| { |
| int cnt=0,ans=0; |
| for(int i=1;i<=m;i++) |
| { |
| int fx=get(E[i].u),fy=get(E[i].v); |
| if(fx==fy)continue; |
| ans+=E[i].w; |
| fa[fx]=fy; |
| cnt++; |
| } |
| if(cnt!=n-1){puts("orz");return;} |
| wr(ans); |
| } |
| int main() |
| { |
| pre(); |
| solve(); |
| return 0; |
| } |
顾名思义就是除开最小生成树,其它所有生成树中最小的那一个。
我们先把最小生成树跑出来,容易想到这两棵树实际上长得没有太大的差别,我们只需要考虑如何加入一条边,删除一条边,使得原来的 MST 变成我们要求的 Second-MST 。
那么很好想到的一种方法就是枚举所有不在 MST 中的边,记其端点为 ,由树的性质可以推知,加入这条边一定就会引入一个环,此时我们在这个环上再删除一条不同的边,就可得到一个新的生成树,要尽量让这个生成树的总权值小,我们很明显就是要删除最大的那一条边。
所以我们的任务就变成了:枚举每一条不在 MST 中的边,查询 到 路径上的边权最大值,用 来更新答案,然后发现好像过不了样例。
实际上就是在 “严格” 这个地方出了问题,也就说我们最后得到的答案必须大于原来的 MST 值,言下之意即:拆环的时候不能拆掉和枚举到的边边权相同的边。那么我们就需要同时维护一个路径次大值来防止这种情况,具体转移是这样的(这里用的是倍增):
| mx[i][j][1]=max(mx[i][j-1][1],mx[fa[i][j-1]][j-1][1]); |
| if(mx[i][j-1][1]!=mx[fa[i][j-1]][j-1][1])mx[i][j][2]=min(mx[fa[i][j-1]][j-1][1],mx[i][j-1][1]); |
| else mx[i][j][2]=max(mx[i][j-1][2],mx[fa[i][j-1]][j-1][2]); |
如果说是 非严格次小生成树的话,那直接只用维护一个路径最大值就可以了,
1.跑mst和进行dfs要分开建图
2.答案要开longlong
3.倍增的时候和求lca的过程不太一样,要注意边界条件
| #include<bits/stdc++.h> |
| using namespace std; |
| const int N=1e6; |
| struct Edge |
| { |
| int u,v,w,nex; |
| }E[N],e[N]; |
| bool cmp(Edge a,Edge b) |
| { |
| return a.w<b.w; |
| } |
| int tote,head[N]; |
| inline void add(int u,int v,int w) |
| { |
| E[++tote].v=v,E[tote].nex=head[u],E[tote].w=w; |
| head[u]=tote; |
| } |
| template<typename T>inline void re(T &x) |
| { |
| x=0; |
| int f=1;char c=getchar(); |
| while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} |
| while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} |
| x*=f; |
| } |
| int n,m; |
| int dep[N],fa[N][25],mx[N][25][3]; |
| int tag[N]; |
| inline void dfs(int x) |
| { |
| for(int i=head[x];i;i=E[i].nex) |
| { |
| int v=E[i].v; |
| if(v==fa[x][0])continue; |
| dep[v]=dep[x]+1,fa[v][0]=x; |
| mx[v][0][1]=E[i].w,mx[v][0][2]=-(1e9+1); |
| dfs(v); |
| } |
| } |
| inline int lca(int x,int y) |
| { |
| if(dep[x]<dep[y])swap(x,y); |
| for(register int i=20;i>=0;--i) |
| if(dep[fa[x][i]]>=dep[y])x=fa[x][i]; |
| if(x==y)return x; |
| for(register int i=20;i>=0;--i) |
| { |
| if(fa[x][i]!=fa[y][i]) |
| x=fa[x][i],y=fa[y][i]; |
| } |
| return fa[x][0]; |
| } |
| inline int q(int val,int x,int y) |
| { |
| int Lca=lca(x,y); |
| int ans=-(1e9+7); |
| for(register int i=20;i>=0;--i) |
| { |
| if(fa[x][i]!=Lca&&dep[fa[x][i]]>dep[Lca]) |
| { |
| if(mx[x][i][1]!=val)ans=max(ans,mx[x][i][1]); |
| else ans=max(ans,mx[x][i][2]); |
| x=fa[x][i]; |
| } |
| } |
| if(x!=Lca&&mx[x][0][1]!=val)ans=max(ans,mx[x][0][1]); |
| for(register int i=20;i>=0;--i) |
| { |
| if(fa[y][i]!=Lca&&dep[fa[y][i]]>dep[Lca]) |
| { |
| if(mx[y][i][1]!=val)ans=max(ans,mx[y][i][1]); |
| else ans=max(ans,mx[y][i][2]); |
| y=fa[y][i]; |
| } |
| } |
| if(y!=Lca&&mx[y][0][1]!=val)ans=max(ans,mx[y][0][1]); |
| return ans; |
| } |
| int f[N]; |
| inline int get(int x){return x==f[x]?x:f[x]=get(f[x]);} |
| long long mst; |
| inline void Kruskal() |
| { |
| sort(e+1,e+m+1,cmp); |
| for(register int i=1;i<=m;i++) |
| { |
| int fx=get(e[i].u),fy=get(e[i].v); |
| if(fx==fy)continue; |
| mst+=e[i].w; |
| f[fx]=fy; |
| tag[i]=1; |
| } |
| } |
| inline void pre() |
| { |
| re(n),re(m); |
| int u,v,w; |
| for(register int i=1;i<=m;++i) |
| re(e[i].u),re(e[i].v),re(e[i].w); |
| for(register int i=1;i<=n;++i)f[i]=i; |
| Kruskal(); |
| for(register int i=1;i<=m;++i) |
| { |
| if(!tag[i])continue; |
| add(e[i].u,e[i].v,e[i].w); |
| add(e[i].v,e[i].u,e[i].w); |
| } |
| dep[1]=1; |
| dfs(1); |
| for(int j=1;j<=20;++j) |
| for(int i=1;i<=n;++i) |
| { |
| fa[i][j]=fa[fa[i][j-1]][j-1]; |
| mx[i][j][1]=max(mx[i][j-1][1],mx[fa[i][j-1]][j-1][1]); |
| if(mx[i][j-1][1]!=mx[fa[i][j-1]][j-1][1])mx[i][j][2]=min(mx[fa[i][j-1]][j-1][1],mx[i][j-1][1]); |
| else mx[i][j][2]=max(mx[i][j-1][2],mx[fa[i][j-1]][j-1][2]); |
| } |
| } |
| inline void solve() |
| { |
| long long ans=1e18+1; |
| |
| for(register int i=1;i<=m;i++) |
| { |
| if(tag[i])continue; |
| int u=e[i].u,v=e[i].v; |
| ans=min(ans,mst-q(e[i].w,u,v)+e[i].w); |
| } |
| cout<<ans; |
| } |
| int main() |
| { |
| pre(); |
| solve(); |
| return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
CF1184E1
一条边的边权待定,如果其在 MST 中,那么它最大的可能边权是多少。
这道题有很多思考方向,首先发现这个边权和被选在 MST 中一定是具有单调性的,所以可以二分加上一个 Kruskal,复杂度是 。
从二分的角度来思考,这个边权大到什么程度我们才不会选这条边?记这条边为 的话,也就是有一条能够链接 和 所在集合,而且边权更小的边出现的时候,那么实际上我们要求的最大答案就是这个边权,容易用反证法证明。
ABC383E
https://www.cnblogs.com/Hanggoash/p/18593277
那么从次小生成树考虑呢?
我们先刨去这条边跑一个 MST,假设这条边初值为 ,我们不断降低他的边权直到它能够被加入 MST 中。
这不就是我们找环上路径最大值的过程吗?甚至不不用维护次大值,因为这不要求 “严格”。
实际上我们会发现,这两种算法最后找到的那条边一定是同一条边。
在mst上面,任意 之间的路径是唯一的,按照kruskal算法选出的用来连接 的边,一定是 路径上的最大值,因为我们是将边权排序后再进行选择的。
关于次小生成树的求法,实际上就是一个维护并查询路径上最大/次大值的过程,可以用LCT来实现,也可以使用倍增,两者的细节不同。
还可以出这样一道题:
一条边 的边权待定,求一个最大的确切范围 ,使得当 的时候, 在这张图的严格次小生成树中(一定有解)。
这就要求我们同上地求出次小生成树 之后,再对待定边加入后构成的环上最大边权值进行查询,记其为 ,那么最后的答案就会是
鸽了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效