您的位置:首页 > 其它

POJ - 3177 Redundant Paths (边双连通 tarjan)

2017-06-28 11:51 281 查看
POJ - 3177

题意+思路:问最少能补成几条边,使这个图成为边双连通。

具体的做法是把每个双连通分量缩成一个点,(求边双连通分量: 先找桥,然后把所有的桥从图中删去,这样图中剩下的几块连起来的子图,每个子图是一个边双连通分量),缩点的时候,把所有的桥加回来,而这题所求的加最少的边来补全成双连通图,ans = (num(leaf) + 1) / 2 ;

原因:缩点后的图,可以看作一棵树,树上的每条边都是需要补充的桥,那么我们肯定是先找树上路径最长的,再找次长的,直到成为一个双连通。树上路径最长的起点和终点肯定是在叶子结点上,所以每次找lca最远的两个叶子结点,把他们连起来,所有偶数个叶子节点的时候,刚好是num(leaf)/2,奇数就是(num(leaf)+1)/2,所以就有了那个式子。

这题的去重我是直接在add()加边的时候操作的,但是这样其实不够快,毕竟每次都要找以u为起点的边。

参考了一下别人的代码,tarjan中会有一个参数fa来判断是否是走过来的那条边,如果用这条边在edge中的下标来传递参数,那么在tarjan中就可以判断一下到底这条边是走过来的那条边,还是重复的边了,如果是重复的边,还是让它去往下去更新,这样就能得到正确的结果。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>

using namespace std;
const int maxe = 1e4 + 10, maxn = 5e3 + 50;
struct node
{
int to,next;
int tag,isb; //判断重边与是否为桥
node(){}
node(int a,int b,int c,int d) {to = a; next = b, tag = c, isb = d;}
}edge[maxe << 2];
int h[maxn], dfn[maxn], low[maxn], bid[maxn] , vis[maxn];
int in[maxn], isb[maxn];
int n,m;
int edgenum,tot;
void init()
{
for(int i = 1; i <= n; i++)
h[i] = -1, low[i] = dfn[i] = bid[i] = vis[i] = 0;
for(int i = 1; i <= n ; i++)
bid[i] = in[i] = isb[i] = 0;
edgenum = tot = 0;
}
void add(int u,int to)
{
int flag = -1; //判重
for(int i = h[u]; ~i; i = edge[i].next)
{
int v = edge[i].to;
if(v == to) {flag = i;break;}
}
if(~flag) {edge[flag].tag = 1; return;}
edge[edgenum] = node(to,h[u],0,0);
h[u] = edgenum++;
}
int tarjan(int u,int pre) //找桥标记一下
{
dfn[u] = low[u] = ++tot;
for(int i = h[u]; ~i ; i = edge[i].next)
{
int v = edge[i].to;
if(!dfn[v])
{
tarjan(v,u);
low[u] = min(low[u],low[v]);
if(!edge[i].tag && low[v] > dfn[u])
{
edge[i].isb = 1;
edge[i^1].isb = 1;
}
}
else if(dfn[v] < dfn[u] && v != pre)
low[u] = min(low[u],dfn[v]);
}
return 0;
}
void shrink(int u,int root,int pre)
{
bid[u] = root;
vis[u] = true;
for(int i = h[u]; ~i; i = edge[i].next)
{
int v = edge[i].to;
if(!edge[i].isb && v != pre && !vis[v])
shrink(v,root,u);
}
}
int solve()
{
for(int i = 1; i <= n ; i++)
for(int j = h[i]; ~j ; j = edge[j].next)
{
int u = i, v = edge[j].to;
if(edge[j].isb == 0) continue; //如果不是桥
in[bid[u]] ++;
}
int ans = 0;

//for(int i = 1;i <= n ; i++)
// cout << isb[i] << " " << in[i] << endl;
for(int i = 1; i <= n ; i++)
if(isb[i] && in[i] == 1) ans++;
printf("%d\n",(ans+1)/2);
return 0;
}
int main()
{
//freopen("D://in.txt","r",stdin);
while(~scanf("%d%d",&n,&m))
{
init();
for(int i = 0; i < m ; i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b); add(b,a);
}
//找桥标记桥
for(int i = 1 ; i <= n ; i++)
if(!dfn[i]) tarjan(i,-1);

/*for(int i = 1; i <= n; i++)
for(int j = h[i]; ~j; j = edge[j].next)
{
int v = edge[j].to;
cout <<"f to isb "<< i << " " << v << " " << edge[j].isb << endl;
}
*/
for(int i = 1 ; i <= n ; i++)
if(!bid[i]) { isb[i] = true; shrink(i,i,-1); }
solve();
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: