• poj3694+hdu2460 求桥+缩点+LCA/tarjan


    这个题使我更深理解了TARJAN算法,题意:无向图,每添加一条边后文桥的数量,三种解法:(按时间顺序),1,暴力,每每求桥,听说这样能过,我没过,用的hash判重,这次有俩个参数(n->10w,开不了二维的),怎么判?联系2个参数,我想到了用一个函数,像散列一样,定义关系,我随便写了一个hash[x+y+x/y+y/x+x%y+y%x+x|y],一直WA,虽然未过,但是想到了这个,以后2个参数判重可以用之!2.网上学习了算法,将之缩点成树,每个双连通分量用一个点表示,用一个数组tree[i],点i属于tree[i]值,然后记录下桥,重新用一个FATHER【i】来建树,具体见代码。3.学习了算法后,其实不用缩点!直接搞起!因为tarjan算法本生成树!用后面一个点来标记边即可啊!(开始总部知道怎么标记!)并更加理解了树枝边和返祖边,桥是树枝边,无向图的所有边分为树枝边和返祖边(后向边),都是实际存在的。直接在原图上找LCA(按DFN值来判断)即可。

    //(原创于2014.2.18)今复习之用,重新理解后,重新编辑方法3。

    在hduj交爆栈,前加一句话:即可AC

    #pragma comment(linker, "/STACK:10240000000000,10240000000000")// 申请空间

    #include<iostream>  //暴力,WA
    #include<vector>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    vector<vector<int> >edge(100001);
    int dfn[100001];
    int low[100001];
    int visited[100001];         //标记访问
    int times=0;                  //时间戳
    int hash[210001];
    int num_bridge=0;
    int min(int a,int b)
    {
        if(a<=b)return a;
        return b;
    }
    void tarjan(int u,int fa)  //dfs
    {
        dfn[u]=low[u]=++times;
        int daxiao=edge[u].size();
        for(int i=0;i<daxiao;i++)
        {
            int child=edge[u][i];
            if(visited[child]==0)
            {
                visited[child]=1;
                tarjan(child,u);
                low[u]=min(low[u],low[child]);
                if(dfn[u]<low[child]&&hash[u+child+2*u%child+2*child%u+3*u/child+3*child/u]<=1)        //是桥
                {
                    num_bridge++;
                }
            }
            else if(edge[u][i]!=fa)
            {
                low[u]=min(dfn[edge[u][i]],low[u]);
            }
       }
    }
    int main()
    {
        int n,m;
        int tcase=1;
         while (~scanf("%d%d",&n,&m)&&(n||m))
         {
             for(int i=0;i<=n;i++)
               {
                   edge[i].clear();
               }
             for(int i=0;i<210001;i++)
             {
                hash[i]=0;
             }
             int a,b;
            for(int i=0;i<m;i++)
            {
                scanf("%d%d",&a,&b);
                edge[a].push_back(b);
                edge[b].push_back(a);
                hash[a+b+2*a%b+2*b%a+3*a/b+3*b/a]++;
            }
            int que;scanf("%d",&que);
        printf("Case %d:
    ",tcase);
        tcase++;
        while(que--)
        {
                times=0;
                num_bridge=0;
            for(int i=0;i<=n;i++)
            {
                low[i]=dfn[i]=visited[i]=0;
            }
               scanf("%d%d",&a,&b);
               edge[a].push_back(b);
               edge[b].push_back(a);
               hash[a+b+2*a%b+2*b%a+3*a/b+3*b/a]++;
             visited[1]=1;
              tarjan(1,-1);
          printf("%d
    ",num_bridge);
        }
        printf("
    ");
         }
            return 0;
    }
    

    #include<iostream> //方法2,poj 2000MS AC,HOJ RE(stack over)
    #include<vector>
    #include<cstring>
    #include<cstdio>
    #include<stack>
    #include<queue>
    using namespace std;
    int dfn[100001];             //
    int low[100001];
    int visited[100001];         //tarjan标记访问
    int father[100001];          //缩点后建成一棵树
    int head[100001];            
    bool instack[100001];         
    int tree[100001];           //每个边双连通分量中的点属于一个集合,函数值1-block
    int level[100001];
    bool mark[100001];           //dfs标记
    bool is_bridge[100001];       //标记桥,形成树后,用点来标记边也可,
    stack<int>s;
    vector<vector<int> >bridge(100001);
    int min(int a,int b)
    {
        if(a<=b)return a;
        return b;
    }
    struct  edges         //边
    {
        int pre,to;
    };
    struct  bridges       //桥
    {
        int from,to;
    };
    int times=0;     int num_bridge=0;   int block;  //时间戳,桥数量,“块”数(边双连通分量数)
    vector<edges>edge(400001);  vector<bridges> ve;
    void tarjan(int u,int fa)          //走一遍,求出桥
    {
        dfn[u]=low[u]=++times;
        instack[u]=1;
        s.push(u);                       //入栈,
        for(int i=head[u];i!=-1;i=edge[i].pre)
        {
            int child=edge[i].to;
            if(visited[child]==0)
            {
                visited[child]=1;
                tarjan(child,u);
                low[u]=min(low[u],low[child]);
                if(dfn[u]<low[child])         //是桥,保存起来
                {
                    num_bridge++;
                    bridges temp;
                    temp.from=u;temp.to=child;
                   ve.push_back(temp);
                }
            }
            else if(child!=fa)
            {
                low[u]=min(dfn[child],low[u]);
            }
       }
       if(dfn[u]==low[u])     //发现一个边双连通分量 blosk++
       {
           block++;
           int now=s.top();
           while(now!=u)
           {
               instack[now]=0;
               s.pop();
               tree[now]=block;
               now=s.top();
           }
           instack[now]=0;
            s.pop();
            tree[now]=block;
       }
    }
    void dfs(int u,int lev)    //走一遍DFS,自制缩成一棵树,用father【i】来连接,规定了方向,并记录每个点深度。
    {
        level[u]=lev;
        int len=bridge[u].size();
        for(int i=0;i<len;i++)
        {
            int v=bridge[u][i];
            if(mark[v]==0)
            {
                father[v]=u;
                mark[v]=1;
                dfs(v,lev+1);
            }
        }
    }
    void lca(int u,int v)    //每次询问添加的边,调用一次LCA,将路经上(按father和深度level向上走)的标记为非桥。
    {
        if(level[u]>level[v]){int temp=v;v=u;u=temp;}
        while(level[v]>level[u])
        {
            if(is_bridge[v])
            {
                num_bridge--;
                is_bridge[v]=0;
            }
            v=father[v];
        }
        while(v!=u)
        {
             if(is_bridge[v])
            {
                num_bridge--;
                is_bridge[v]=0;
            }
             if(is_bridge[u])
            {
                num_bridge--;
                is_bridge[u]=0;
            }
            v=father[v];
            u=father[u];
        }
    }
    int main()
    {
        int n,m;
        int tcase=1;
         while (~scanf("%d%d",&n,&m)&&(n||m))
         {
             for(int i=0;i<100001;i++)
             {
                level[i]=mark[i]=tree[i]=father[i]=low[i]=dfn[i]=visited[i]=0;
                head[i]=-1;
                bridge[i].clear();
                is_bridge[i]=1;
             }
             ve.clear();
             int a,b;
            num_bridge=block=times=0;
            for(int i=0;i<2*m;i++)  //读入
            {
                scanf("%d%d",&a,&b);
                edge[i].to=b;
                edge[i].pre=head[a];
                head[a]=i;
                i++;
                edge[i].to=a;
                edge[i].pre=head[b];
                head[b]=i;
            }
              visited[1]=1;
    
              tarjan(1,-1);
    
           for(int i=0;i<ve.size();i++)
           {
               bridge[tree[ve[i].from]].push_back(tree[ve[i].to]);
               bridge[tree[ve[i].to]].push_back(tree[ve[i].from]);
           }
           mark[1]=1;
           dfs(1,0);
            int que;scanf("%d",&que);
        printf("Case %d:
    ",tcase);
           tcase++;
        while(que--)
        {
            scanf("%d%d",&a,&b);
            lca(tree[a],tree[b]);
            printf ("%d
    ",num_bridge);
        }
        printf("
    ");
         }
            return 0;
    }
    

    方法3:

    #pragma comment(linker, "/STACK:10240000000000,10240000000000")// 申请空间,否则爆。Hdu 460MS ac
    #include<iostream> //poj 1000MS AC,
    #include<vector>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    int dfn[100001];
    int low[100001];
    bool visited[100001];         //tarjan标记访问
    int father[100001];          //缩点后建成一棵树
    int head[100001];         //每个边双连通分量中的点属于一个集合,函数值1-block
    bool is_bridge[100001];       //标记桥,形成树后,用点来标记边也可,
    int min(int a,int b)
    {
        if(a<=b)return a;
        return b;
    }
    struct  edges         //前向星保存边
    {
        int pre,to;
    };
    int times=0;     int num_bridge=0;    //时间戳,桥数量,
    vector<edges>edge(400001);           
    void tarjan(int u,int fa)          //走一遍,求出桥,不忘了。dfs生成的是树!所以可以标记后面一个点来标记边!无向图tarjan俩个变量
    {
        dfn[u]=low[u]=++times;
        for(int i=head[u];i!=-1;i=edge[i].pre)
        {
            int child=edge[i].to;
            if(visited[child]==0)    //理解这里是树枝边(向前)!
            {
                visited[child]=1;
                father[child]=u;        //生成树的父亲
                tarjan(child,u);
                low[u]=min(low[u],low[child]);
                if(dfn[u]<low[child])         //是桥,保存起来,标记后一个点即可(生成树每个点对应一条到它的边)
                {
                     num_bridge++;
                    is_bridge[child]=1;      
                }
            }
            else if(child!=fa)       //返祖边(向后边)点已经访问,说明child是u的某个祖先!
            {
                low[u]=min(dfn[child],low[u]);
            }
       }
    }
    void lca(int u,int v)    //每次询问添加的边,调用一次LCA,将路经上(按father和dfn向上走)的标记为非桥。dfn恰好是树的的层次。
    {
        if(dfn[u]>dfn[v]){int temp=v;v=u;u=temp;}
        while(dfn[v]>dfn[u])      //到同一层为止。
        {
            if(is_bridge[v])
            {
                num_bridge--;
                is_bridge[v]=0;
            }
            v=father[v];
        }
        while(v!=u)      //到公共祖先为止。
        {
             if(is_bridge[v])
            {
                num_bridge--;
                is_bridge[v]=0;
            }
             if(is_bridge[u])
            {
                num_bridge--;
                is_bridge[u]=0;
            }
            v=father[v];
            u=father[u];
        }
    }
    int main()
    {
        int n,m;
        int tcase=1;
         while (~scanf("%d%d",&n,&m)&&(n||m))
         {
             for(int i=0;i<100001;i++)
             {
                dfn[i]=father[i]=low[i]=dfn[i]=visited[i]=0;
                head[i]=-1;
                is_bridge[i]=0;
             }
             int a,b;
            num_bridge=times=0;
            for(int i=0;i<2*m;i++)  //读入
            {
                scanf("%d%d",&a,&b);
                edge[i].to=b;
                edge[i].pre=head[a];
                head[a]=i;
                i++;
                edge[i].to=a;
                edge[i].pre=head[b];
                head[b]=i;
            }
              visited[1]=1;
              tarjan(1,-1);
            int que;scanf("%d",&que);
        printf("Case %d:
    ",tcase);
           tcase++;
        while(que--)
        {
            scanf("%d%d",&a,&b);
            lca(a,b);
            printf ("%d
    ",num_bridge);
        }
        printf("
    ");
         }
            return 0;
    }
    


  • 相关阅读:
    5773. 【NOIP2008模拟】简单数学题
    jzoj_5455. 【NOIP2017提高A组冲刺11.6】拆网线
    5461. 【NOIP2017提高A组冲刺11.8】购物
    博客第三天
    博客第二天
    博客的第一天。
    微服务架构下的session一致性
    分布式数据库数据一致性的原理、与技术实现方案
    epoll的本质
    DNS域名解析过程
  • 原文地址:https://www.cnblogs.com/yezekun/p/3925817.html
Copyright © 2020-2023  润新知