【BCC】冗余路径(做法 + 证明)
题意
给定一个连通的无向图让你进行加边操作,要求每一对点之间都至少有两条相互分离的路径,求最小的加边数。
两条路径相互分离,是指两条路径没有一条重合的道路。
分析
这意味着,从一个点到另一个点,不能够存在一条边满足:如果不经过这条边,这个点就到不了另一个点。
换句话说,就是不能存在一条边,使得去掉这条边后图不连通,那么题目要求的就是:给连通的无向图加边,使得无向图没有桥(即变成边双连通分量),最小化加边数。
做法
因为边双连通分量本来就没有桥,所以我们考虑对整个图求一遍边双连通分量(使用 tarjan 算法),然后将边双连通分量缩为一个点考虑。那么缩完点后得到的图一定是一棵树(因为图中不可能存在环)。
先给出结论:
所加的边数至少为 ( 为叶结点个数),而这恰好就是答案。
证明
下面,我们要证明的是:
- 加边数至少为
- 给连通的无向图加 ( 为叶结点个数)条边即可保证所得的图为边双连通分量
先证明 1:因为对于一个叶子节点 ,如果把它与父节点相连的边割去会让它成为独立点,所以每个叶子节点都需要向其它点连一条边,因此加边数至少为 。
下证 2:
当图的点数 为 时,两个点都是叶节点,结论成立。
考虑 的情况:
我们一定可以找到一个度数大于 的点,我们将它作为根节点 ,直观的构造方法是:
叶子节点取 个点与另外 个一一相连,如果多出一个点则向根节点连接。
因此,我们只需要考察 为偶数的情况, 为奇数时多出来的一个点向根节点连接即可。
但是这个构造方法是要满足一定的约束的,我们要证明在这个约束下仍保证这个构造方法可以实行。下面我们便说明这件事。
根节点下有若干棵子树,设有 棵。
每个子树有若干个叶子节点,个数记为 。方便起见,我们排好了序()。
所谓约束,就是同一棵子树的叶节点之间不可以相连,因为即便是相连了, 与该子树的边依然是桥,如果同一棵子树的叶节点之间存在连边,将次边去掉答案将更优。
下证:不同的子树的叶节点之间连边一定可以得到最优解。
第 棵子树间的叶节点连边,等价于 同时减去 。因此连边操作现在等价于:从数列 中选择两个数同时 。(记为操作)
我们先证明: 时,一定能通过有限次上述操作使得最后的数列全部为 :
构造方法:
将左式 的最小值与 同时 ,如果左式最小值减为 了就移除,如果出现左式中的 的情况,就将二者调换,继续操作。
最后,我们要证明的是:给出一个 的树,一定存在一个点作为根,使得 子树叶节点树所对应的数列 满足 :
构造方法:任意选取一个度数不为 的点作为根,如果 我们就换根,让 所对应的子树的顶点作为根,直到对应的序列满足 ,这一定是有解的。
证毕。
证明是我乱想出来的,有矛盾的地方请告诉我qwq。
#include<bits/stdc++.h>
using namespace std;
const int N=5005, M=2e4+5;
int n, m;
struct node{
int to, next;
}e[M];
int h[N], tot;
void add(int u, int v){
e[tot].to=v, e[tot].next=h[u], h[u]=tot++;
}
int dfn[N], low[N], ts;
int stk[N], top;
int id[N], bcc_cnt;
bool is_bridge[N];
void tarjan(int u, int from){ // 起始点和从前而来的边
dfn[u]=low[u]=++ts;
stk[++top]=u;
for(int i=h[u]; ~i; i=e[i].next){
int go=e[i].to;
if(!dfn[go]){
tarjan(go, i);
low[u]=min(low[u], low[go]);
if(dfn[u]<low[go]) is_bridge[i]=is_bridge[i^1]=true;
}
else if(i!=(from^1)) // 非反向边
low[u]=min(low[u], dfn[go]);
}
if(dfn[u]==low[u]){
++bcc_cnt;
int y;
do{
y=stk[top--];
id[y]=bcc_cnt;
}while(y!=u);
}
}
int deg[N];
int main(){
memset(h, -1, sizeof h);
cin>>n>>m;
while(m--){
int u, v; cin>>u>>v;
add(u, v), add(v, u);
}
tarjan(1, -1);
for(int i=0; i<tot; i++) if(is_bridge[i]) deg[id[e[i].to]]++;
int cnt=0;
for(int i=1; i<=bcc_cnt; i++) if(deg[i]==1) cnt++;
cout<<(cnt+1)/2<<endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】