POJ 3352 || POJ3177

开始的时候是去做论坛上的poj上的2942,但是发现我不会做,于是去查关于双联通的问题,然后就看到了tarjan算法,记得国庆在家的时候,就想学来着,结果本身对于强连通什么的基础的东西都没有弄懂,跟不要说去看tarjan算法了。。。全是一团雾水。一周前各种找资料,但是发现很多博主没有真正的理解tarjan,于是混用,找了很多份博客来看由于大家的理解是不同的于是我越来越糊涂。后来看到一个博客里面提到黑书。我觉得黑书里面对于双联通的讲解很是清晰,而且按那样来写的话,对于割边和割点也很容易理解,推荐之~~~在tarjan里面有一个很重要的判断,就是low[i]>dfn[u],其中u是父亲节点,i是u的儿子,相对于什么父子边(树枝边)来说的。对于割点来讲是low[i]>=dfn[u],对于割边来讲是low[i]>dfn[u]。当将用tarjan写的双联通的时候就会出现问题,因为这个算法的最后是low[u]=min(low[u],dfn[v]);其中并没有将父亲节点排除在外,也就是说,不能判断它是否走了回路,还是只是沿着刚刚来的边回去了而已。所以不能直接的改那个判断条件,那样根本就得不到割边,我就用这个人中的tarjan,然后又用那个人中的改变条件的那个理论,所以困惑了很长的时间。最后还有一点最开始的时候还很是纠结怎样把联通分量求出来,现在知道了。就是将所有的桥断开,然后染色。这步也可以用来缩点。还有一个定理就是缩完点之后要使一个图为双联通所加的最小边就是度为一的点的个数num,(num+1)/2。原理就是将两个度为一的点连接,然后再缩点,再连接,再缩点。。。收获很大,也让我明白了,只是用模板而不真正理解算法本身是学不到什么的,终会出错了。

上代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[1010][1010];
int dfn[1010],low[1010],root,c[1010],n,father[1010],vis[1010],num,d[1010],q[1010],brige=0;
void dfs(int k,int f,int deep){
int i,tot;
c[k]=1;
dfn[k]=deep;
low[k]=deep;
tot=0;
for(i=1;i<=n;i++){
if(a[i][k]==1 && c[i]==1 && i!=f)
low[k]=min(low[k],dfn[i]);
if(a[i][k]==1 && c[i]==0){
dfs(i,k,deep+1);
tot=tot+1;
low[k]=min(low[k],low[i]);
if(low[i]>dfn[k]){
a[i][k]=2;
a[k][i]=0;
}
}
}
c[k]=2;
}
void bfs(int u){
int h=0,r=0,i;
father[u]=num;
vis[u]=1;
q[r++]=u;
while(h!=r){
int t=q[h++];
father[t]=num;
for(i=1;i<=n;i++){
if(!vis[i] && a[i][t]==1){
q[r++]=i;
vis[i]=1;
}
}
}
}
main(){
int m,i,x,y,ans=0,j;
scanf("%d %d",&n,&m);
memset(a,0,sizeof(a));
for(i=1;i<=m;i++){
scanf("%d %d",&x,&y);
a[x][y]=1;
a[y][x]=1;
}
memset(dfn,-1,sizeof(dfn));
memset(low,0,sizeof(low));
memset(c,0,sizeof(c));
for(i=1;i<=n;i++){
if(dfn[i]==-1){
root=i;
dfs(i,i,0);
}
}
num=1;
        memset(vis,0,sizeof(vis));
        memset(d,0,sizeof(d));
        memset(father,-1,sizeof(father));
        for(i=1;i<=n;i++){
        if(father[i]==-1){
        bfs(i);
        num++;
       }
        }
        for(i=1;i<=n;i++){
        for(j=1;j<=n;j++){
        if(a[i][j]==2){
        d[father[i]]++;
        d[father[j]]++;
        }
       }
        }
        for(i=1;i<=num-1;i++){
        if(d[i]==1)
        ans++;
        }
        printf("%d\n",(ans+1)/2);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值