题意: 一个有 N 个景点的岛,任意两个景点都有道路相连,当道路施工时,游客便不能在该道路上通行,问至少再增加几条道路可以使得在任一条道路维修的情况下,游客都能从岛上任意一个景点到达另一个景点。
分析: 当原图中存在桥的时候,即原图不是双连通图的时候,目的就无法达到,所以这题的关键在于 需要增加几条边可以使得原图中不存在桥。
可以先找出原图中所有的边双连通分量,对其进行缩点,缩点具体做法是将 图中low值相同的节点看作一个点, 缩点后,原图可以看成是一颗树,而要使得一棵树变为一个双连通图,有一个定理: 增加的边数 = (树中总度数为1的节点数+1)/ 2
转一大神分析:
对于这种题,我们正常的做法是求桥,删桥,求连通分支,缩点,构建新图,求叶子数。 我们有一种简便方法。需要对tarjan算法做一些变化。我们之前规定low[u]是其子孙通过一条返祖边直接到达的点,把这个改成是其子孙可以连续通过多条返祖边所能到达的点。那么low[u]=min(low[v],dfn[u]); 这样做的缺陷是,不能求割点了,多次返祖会导致求割点的错误,在多环两两以单个点相连排成一条线,且每两个连接点间只有一条边的情况中,那些连接点本应是割点,但是在dfs过程中,这些连接点之间的边又恰好不是树枝边的话,low[u]可能会通过多次返祖,从一个割点不断的经过这些割点到达最上边的割点才记录下low[u]。 这样中间的割点就都不符合dfn(u)<=low[v]了。 但是这样做有一个好处,就是所有的对于边的双连通分支都以low标记出来了,即属于同一双连通分支的所有点的low都等于同一个值。因为在不遇到桥的情况下,low可以返祖到该连同分支在遍历树中的最高点(dfn最小的点)。 这样就相当于整理出了所有的对于边的双连通分支。我们直接遍历所有的边,观察边的两端点是否属于同一分支,若不属于则修改两点的度数。然后看有多少个度数为1的点即可。
#include<stdio.h> #include<string.h> #define clr(x)memset(x,0,sizeof(x)) const int maxn=5005; struct node { int to,next; }e[10005]; int tot; int head[maxn]; void add(int s,int u) { e[tot].to=u; e[tot].next=head[s]; head[s]=tot++; } int ti,top,sn; int low[maxn]; void tarjan(int p,int u) { low[u]=++ti; int k,i; for(i=head[u];i;i=e[i].next) { k=e[i].to; if(k==p) continue; if(low[k]==0) tarjan(u,k); if(low[k]<low[u]) low[u]=low[k]; } } int main() { int n,m,re,i,j,k,a,b; int degree[maxn]; scanf("%d%d",&n,&m); ti=0; tot=1; clr(head); clr(low); while(m--) { scanf("%d%d",&a,&b); a--;b--; add(a,b); add(b,a); } tarjan(0,0); clr(degree); for(i=0;i<n;i++) for(j=head[i];j;j=e[j].next) { k=e[j].to; if(low[k]!=low[i]) degree[low[i]]++; } re=0; for(i=1;i<=ti;i++) if(degree[i]==1) re++; printf("%d\n",(re+1)/2); return 0; }