Time Limit: 1000MS | Memory Limit: 65536K | |
Description
Input
Output
Sample Input
7 7 1 2 2 3 3 4 2 5 4 5 5 6 5 7
Sample Output
2
Hint
1 2 3 +---+---+ | | | | 6 +---+---+ 4 / 5 / / 7 +Building new paths from 1 to 6 and from 4 to 7 satisfies the conditions.
1 2 3 +---+---+ : | | : | | 6 +---+---+ 4 / 5 : / : / : 7 + - - - -Check some of the routes:
题目大意:给定一个无向图,求加入最少的边,使该图变成一个双连通分量?
点双连通分量:不存在割点的连通分量
边双连通分量:不存在桥的连通分量(即任意两点互相可达的路径有不同的两条)
大致方法:可以求出所有的桥,把桥删掉。然后把所有的边双连通分量求出来,显然这些边双连通分量就是原图中的双连通分量。把它们缩成点,然后添上刚才删去的桥,就构成了一棵树。在树上添边使得树变成一个双连通分量即可。可以统计该树度为1的叶子节点(设共有x个),则答案为:(x+1)/2
注意:题目数据会给出重边,但是重边只算一次(即题目意思是两点之间有路,这个信息可能会重复出现,但实际上只有一条路),但是我看有一个题解没有判断重边也过了,很是费解(这个题解用数组模拟的邻接表,判断父子边时,由于父子边的下标只差1,所以通过异或运算判断,但是不能判断重边,然后就不明白了)
附上课件中的证明:
命题:一棵有n(n>=2)个叶子结点的树,至少(只需)要添加ceil(n/2)条边,才(就)能转变为一个没有桥的图。或者说,使得图中每条边,都至少在一个环上。
证明:
这里只证明n为偶数的情况。n为奇数的证明类似。
先证明添加n/2条边一定可以达成目标。
n=2时,显然只需将这两个叶子间连一条边即可。命题成立。
设n=2k(k>=1)时命题成立,即AddNum(2k)=k。下面将推出n=2(k+1)时命题亦成立
n=2k+2时,选取树中一条迹(无重复点的路径),设其端点为a,b;并设离a最近的度>=3的点为a',同理设b'。
(关于a‘和b’的存在性问题:由于a和b的度都为1,因此树中其它的树枝必然从迹<a,b>之间的某些点引出。否则整棵树就是迹<a,b>,n=2<2k+2,不可能。)
a’ b’不重合时:
在a,b间添一条边,则迹<a,b>上的所有边都已不再是桥。这时,将刚才添加的边,以及aa‘之间,bb’之间的边都删去,得到一棵新的树。因为删去的那些边都已经符合条件了,所以在之后的构造中不需要考虑它们。由于之前a‘和b’的度>=3,所以删除操作不会使他们变成叶子。因此新的树必然比原树少了两个叶子a,b,共有2k个叶子。由归纳知需要再加k条边。因此对n=2k+2的树,一共要添加k+1条边。
将a和一个非b的叶子节点x连上,然后将环缩点至 a’。
因为叶子节点是偶数,所以必然还存在一个非b非x的叶子节点不在环上, 因此a’不会变成叶子节点,于是新图比原图少2个叶子节点。
再证明n/2是最小的解。
显然,只有一个叶子结点被新加的边覆盖到,才有可能使与它相接的那条边进入一个环中。而一次加边至多覆盖2个叶子。因此n个叶子至少要加n/2条边。
证毕。
#include <cstdio> #include <cstring> #include <vector> using namespace std; const int MAXN=5005; int n,m,num,ans,cnt,top; int stak[MAXN],low[MAXN],dfn[MAXN],color[MAXN],deg[MAXN];//biocks[i]表示删掉i点后,能形成的连通块数 vector<int> g[MAXN]; bool isIn[MAXN],vis[MAXN][MAXN]; void Tarjan(int u,int p) {//求无向图的边双连通分量的Tarjan与求有向图的强连通分量的Tarjan好像... stak[++top]=u; isIn[u]=true; dfn[u]=low[u]=++num; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(v!=p) { if(dfn[v]==0) { Tarjan(v,u); low[u]=min(low[u],low[v]); } else if(isIn[v]&&dfn[v]<low[u]) low[u]=dfn[v]; } } if(dfn[u]==low[u]) { ++cnt; do { v=stak[top--]; isIn[v]=false; color[v]=cnt;//一个边双连通分量缩成一个点 } while(v!=u); } } int solve() { cnt=num=top=0;//cnt表示边双连通分量的个数 memset(dfn,0,sizeof(dfn)); memset(isIn,false,sizeof(isIn)); for(int i=1;i<=n;++i) { if(dfn[i]==0) Tarjan(i,0); } memset(deg,0,sizeof(deg)); for(int i=1;i<=n;++i) { for(int j=0;j<g[i].size();++j) if(color[i]!=color[g[i][j]])//若这两个点不再同一个双连通分量中 ++deg[color[i]];//由于是无向图,所以只对起点的度+1即可 } int ans=0; for(int i=1;i<=cnt;++i) if(deg[i]==1) ++ans; return (ans+1)>>1; } int main() { int s,e; while(scanf("%d%d",&n,&m)==2) { for(int i=1;i<=n;++i) { g[i].clear(); for(int j=i;j<=n;++j) vis[i][j]=vis[j][i]=false; } while(m-->0) { scanf("%d%d",&s,&e); if(!vis[s][e]) {//去掉重边 vis[s][e]=vis[e][s]=true; g[s].push_back(e); g[e].push_back(s); } } printf("%d\n",solve()); } return 0; }