题目描述
给出一张无向连通图,求加最少的边使得图上任两个节点之间都至少有两条路径。
思路
题目的意思就是加边使原图成为一张边双联通图。我们考虑对无向图进行缩点,这里的缩点实质上就是把图中的边双联通分量缩为一个点。我们考虑对于一张图,显然不需要在双联通分量内加边,我们只需要在双连通分量之间加边。接下来考虑缩点之后的图,最后一定是一棵树,若不是一棵树必定会有环,有环就应该被缩点。接下来就是这棵树上的叶子节点,可以知道如果要使这张图成为边双联通图,肯定不能存在度为1的点,否则必定只有一条路径。
不过对于叶子节点我们可以证明必定存在一种加边方法使得其为边双联通分量,并且这种方案是最少的,数目为(leaf+1)/2。我们先考虑特殊情况,即只有一个叶子节点的情况,那么显然原图已经是一个边双联通图,无需再考虑加边。否则再考虑有两个叶子节点,那么原图即为一条链,显然只要加一条边。其他情况,对于每个度为1的叶子节点,我们连一条边每次选两个叶子节点,这两个叶子节点的路径上至少有两条连出去的边,可以证明,只有叶子节点数大于3,肯定能找到这样两个点,而叶子结点数为3需要连两条边,所以按照这种方案我们肯定能取到这个加边,并且将一条边的发挥到极致,因此这是最优方案。
需要注意的是统计边数时不要重复计边,如果用前向星存图要判重,不过也可以另开数组存。
代码
#include <bits/stdc++.h> using namespace std; const int N=5500,M=20020; struct Edge { int x,y; }e[M]; int nxt[M],to[M],head[N],tot; void add_edge(int x,int y) { nxt[++tot]=head[x]; head[x]=tot; to[tot]=y; } int read() { int res=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+ch-'0';ch=getchar();} return res*w; } int dfn[N],low[N],st[N],vst[M],top,col,idx,co[N]; void tarjan(int u) { dfn[u]=low[u]=++idx; st[++top]=u; for(int i=head[u];i;i=nxt[i]) if(!vst[(i&1)?i+1:i-1]) { vst[i]=1; int v=to[i]; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else low[u]=min(low[u],dfn[v]); } else vst[i]=1; if (dfn[u]==low[u]) { co[u]=++col; while(st[top]!=u) { co[st[top]]=col; --top; } --top; } } int deg[N]; int main() { int n,r; n=read();r=read(); for(int i=1;i<=r;i++) { int x=read(),y=read(); add_edge(x,y);add_edge(y,x); e[i].x=x;e[i].y=y; } for(int i=1;i<=n;i++) if(!dfn[i])tarjan(i); for(int i=1;i<=r*2;i++) { // cout<<i<<' '<<cut[i]<<endl; int u=co[e[i].x],v=co[e[i].y]; // cout<<u<<' '<<v<<endl; if(u!=v) { deg[u]++; deg[v]++; } } int cnt=0; for(int i=1;i<=col;i++) if(deg[i]==1)cnt++; printf("%d",(cnt+1)/2); }