kuangbin专题九 POJ 3694(割边+LCA)

题意:
给你n个点和m条边,然后会有q次添加边,问你添加上当前边之后,剩下的桥的个数是多少。
题解:
求割边+LCA,今天看了一整天的博客,都没想着怎么回事,为什么要用LCA,为什么求割边之后就成为一棵树了,终于在晚上想通了。之前一直在想为什么要用LCA来做这道题,原来他们缩点(缩点之后,那些缩点机会里面的边相互联通)之后会形成一棵树,然后因为已经经过缩点了,所以这些树上的边都是桥(终于理解为什么他们说缩点之后的树边为桥了),那么如果加入的这条边是属于一个缩点的话(缩点里面的点算是一个集合)那么就对原图中的桥没有任何影响,但是如果加入的边是属于两个缩点的话,那么就会形成一个环,那么任意删除这个环里面的一条边,这棵树还是互通的。ORZ终于理解了,那么就可以利用LCA的特性去算出到底减少了多少条桥了,因为是最近公共祖先,那么新加入的这条边的两个点通过LCA找到对方肯定是走最短的路径(在树上走最小的边)那么就可以得到结果了,总桥数减去走LCA上的边就是题目要的答案了!!!!哈哈我终于想通了!
注意是求每次增加之后的剩余的桥数,不是在原图基础上只是加这条,而是要考虑之前连了的边,每次回答是在上一次的基础之上)
但是尴尬的是,有一些博客的代码有一些数据过不了,害我又找了一个晚上,差点通宵。数据的话看看我的代码,如果是我代码中提到的那种情况的话,可以去试一下。

#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=100000+7;
vector<int>map[MAXN];
int dfn[MAXN];
int low[MAXN];
int fa[MAXN];
bool vis[MAXN];
int bridge[MAXN];
int Index;
int n,m,ans;
void init()
{
    memset(vis,false,sizeof(vis));
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    memset(bridge,0,sizeof(bridge));
    for(int i=1;i<=n;i++)
    fa[i]=i,map[i].clear();
    Index=0;
    ans=0;
}
void Tarjan(int u,int pre)
{
    vis[u]=true;
    dfn[u]=low[u]=++Index;
    for(int i=0;i<map[u].size();i++)
    {
        int v=map[u][i];
        if(!dfn[v])
        {
            fa[v]=u;
            Tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])
            {
                ans++;
                bridge[v]=1;
            }
        }
        else if(pre!=v)
        low[u]=min(low[u],dfn[v]);
    }
}
void lca(int u,int v)
{
/*  if(dfn[v]<dfn[u])//如果不弄这一步的话,就要分两种情况,一种是dfn[u]>dfn[v],或者是dfn[v]>dfn[u]的情况。 
    swap(u,v);      //但是事实证明有这一步会出现错误,不相信的话可以测试下面这组数据 
    while(dfn[v]>dfn[u])
    {
        if(bridge[v]) ans--,bridge[v]=0;
        v=fa[v];
    }*/ 
/*
6 5
1 2
2 4
2 3
3 5
4 6
1
5 6
这种数据的答案应该是 
Case 1:
1 
或者换成 
6 5
1 2
2 3
2 4
3 5
4 6
1
5 6
*/      
    //还是分别操作两次好点。不然的话就有可能出现一个找到了最近公共祖先,两者不相等,找到祖先的那个再找父亲的话就会导致超过的最近公共祖先了,导致出现错误,应该两个都找到缩点先。 
    while(u!=v)
    {
        while(dfn[v]>dfn[u])//这样做相当于找到强连通分量(类似于找到一个所有点相互联通的集合)(类似于找到一个缩点为止,只是我这里没写缩点,类似于缩点是思想,因为缩点里的点任意联通) 
        {
            if(bridge[v]) ans--,bridge[v]=0;
            v=fa[v];
        }
        while(dfn[v]<dfn[u])
        {
            if(bridge[u]) ans--,bridge[u]=0;
            u=fa[u];
        }
    //    printf("%d %d\n",u,v);
    //    printf("fa %d %d\n",fa[u],fa[v]);
    }
}
int main()
{
    int k=1;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)
        break;
        init();
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            map[u].push_back(v);
            map[v].push_back(u);
        }
        ans=0;
        Tarjan(1,1);
        int q;
        scanf("%d",&q);
        printf("Case %d:\n",k++);
        for(int i=1;i<=q;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            lca(u,v);
            printf("%d\n",ans);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值