zoukankan      html  css  js  c++  java
  • taj

    首先,taj是用来干嘛的?

    taj主要处理两种问题:

    1.找强连通分量

    2.找割点

    均可在O(n)时间内完成

    taj的大体思路就是把一张图改成一棵树,钦定一个点,作为根;

    然后我们有两个数组:

    dfn[u]:u的时间戳

    low[u]:u这一坨东西的最早出现的时间(感性理解)


    求强联通分量:

    强连通分量:有向图中,如果一坨东西,能够互相到达,就是强连通分量,一个点也是

    我们会发现,如果u的low>=dfn(实际上low不会>dfn最多=),那么它一定与上面没有联系,所以我们不用考虑它是否会与上面形成强联通分量

    在它下面的东西(v)中,我们考虑:

    如果说,v的low没有到达u,也就是v无法回到u,那它肯定会在之前就从栈里弹出

    如果说,v的low能到达u的上面,那么u一定可以通过v这一条路到达上面,与上面产生联系,low[u]!=dfn[u]

    所以,只要当low[u]=dfn[u]时,将栈中到u为止的元素都合并到一个强联通分量重,肯定是没问题的

    void taj(int u)
    {
        dfn[u]=low[u]=++tim;
        sta[++top]=u;
        vis[u]=1;
        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                taj(v);
                low[u]=min(low[u],low[v]);
            }
            else if(vis[v]) low[u]=min(low[v],low[u]);
        }
        if(dfn[u]==low[u])
        {
            int v;
            while(v=sta[top--])
            {
                rt[v]=u;
                vis[v]=0;
                if(u==v) break;
                a[u]+=a[v];
            }
        }
    }

    求割点:

    割点:在一个无向连通块中,删去一个点,将导致它不连通的点

    然后就是怎么求:

    图

    如图,我们将一张图抽象如上,1表示图的上部,5表示下部,中间由环相连。

    我们用dfn[x]表示x被访问到的时间,low[x]表示它所在的那一坨的最小值 ~~(感性理解一下)~~

    我们从点1开始dfs,记录每一个点的时间戳,然后去搜所有没有搜过的,如果,他的儿子的最上面的时间戳(low[v])

    low[v]>=dfn[x]说明v最早也是在x及之后了,也就意味着x上面的与v只能通过x相连,也就是说,只要x上面有(x不为本组祖先),那么x就是割点**

    如果x是祖先呢?

    那我们将x找到一个儿子便让x的儿子++,并让儿子搜一遍(保证和此儿子联通的都被搜过),那么下次找到的儿子就是和此儿子不连通的,那么只要把x去掉,他两个儿子就不连通了,所以,x为割点

    树

    不理解上面的算法结合图看,把①去掉,2,3这两个儿子就不连通了

    还有一个问题:大佬都说这里low要这样写:low[x]=min(low[x],dfn[v])

    至于为什么不像强联通分量里一样,写low[x]=min(low[x],low[v])呢?

    因为强联通里只要在强联通中,怎么连边都可以(我们又不会在强联通中割点),但割点不行,我们不能随便连边,low[x]=min(low[x],low[v])表示所有v都连与最先祖先的上,如下图1,但很明显,3,4,5并没有这一条路,所以如果你这么写,代码会认为有这一条路,从而出错

    当然,由图的关系得到的low并不会有假边所以写low[x]=min(low[x],low[v])(不理解看代码)

    实际:

    true

    写low[x]=min(low[x],low[v]):

    fake

    当然,你也可以这么理解:

    这主要是防止它无向图咋搜下来的,就咋走回去了

    void tag(int x,int zx)
    {
        int kid=0;
        dfn[x]=low[x]=++tim;
        for(int i=head[x];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                tag(v,zx);
                low[x]=min(low[v],low[x]); //是有真边构成,不用改
                if(low[v]>=dfn[x]&&x!=zx) cut[x]=1;//后面无法通过别的方法连接到前面,是割点
                if(x==zx) kid++;
            }
            low[x]=min(low[x],dfn[v]); //要改的指这个
        }
        if(kid>1&&x==zx) cut[x]=1; //祖先有两个+的孩子,是割点
    }
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5+5;
    int n,m;
    struct edge
    {
        int next,to;
    }p[N];
    int head[N],num;
    void ad(int x,int y)
    {
        p[++num].next=head[x];
        p[num].to=y;
        head[x]=num;
    }
    int dfn[N],low[N],tim,cut[N];
    void tag(int x,int zx)
    {
        int kid=0;
        dfn[x]=low[x]=++tim;
        for(int i=head[x];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                tag(v,zx);
                low[x]=min(low[v],low[x]);
                if(low[v]>=dfn[x]&&x!=zx) cut[x]=1;
                if(x==zx) kid++;
            }
            low[x]=min(low[x],dfn[v]);
        }
        if(kid>1&&x==zx) cut[x]=1;
    }
    int ans;
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            ad(x,y);
            ad(y,x);
        }
        for(int i=1;i<=n;i++) if(!dfn[i]) tag(i,i);
        for(int i=1;i<=n;i++) ans+=cut[i];
        printf("%d
    ",ans);
        for(int i=1;i<=n;i++)
            if(cut[i]) printf("%d ",i);
        return 0;
    }

    同是,注意:

    正确:

        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                taj(v,zx);
                low[u]=min(low[u],low[v]);
                if(u==zx) kid++;
                if(low[v]>=dfn[u]&&u!=zx) cut[u]=1;
            }
            else if(vis[v]) low[u]=min(low[u],dfn[v]);
            
        }

    错误:

        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                taj(v,zx);
                low[u]=min(low[u],low[v]);
                if(u==zx) kid++;
                
            }
            else if(vis[v]) low[u]=min(low[u],dfn[v]);
            if(low[v]>=dfn[u]&&u!=zx) cut[u]=1;
        }

    就是这个判断的时候,一定是要

    只有直接儿子才可以按if(low[v]>=dfn[u]&&u!=zx) cut[u]=1;判,毕竟人家就是这么走的,但是如果是间接儿子,那么,实际上它应该是从别处走来的,所以这么写是错的


    看一道题:

    lgP3469:https://www.luogu.com.cn/problem/P3469

    题目大意,n个点,m条边,(<=1e5),本来每两个点间可以互相到达,现在把点i给封锁,有多少到达关系会无法成立

    分析:

    如果封锁的点不是割点,那么显然:只有这个点到其他n-1个点和反过来的关系无法做到,所以答案为:2*(n-1)

    如果封锁的是割点,那么这张图将被分割为若干块,若一块的大小为s,那么它与外界(n-s)个点的关系无法成立,所以这个联通块的贡献是s*(n-s);当然,被封锁的点也无法与外界联系,所以有(n-1)的贡献;总贡献就是所有连通块的贡献+n-1

    这样子,我们就可以得到一个O(n^2)的做法:对于每个割点O(n)扫连通块

    #include <bits/stdc++.h>
    #define random(a,b) rand()%(b-a+1)+a
    using namespace std;
    const int N = 1e5+5;
    int n,m;
    struct edge
    {
        int next,to;
    }p[10*N];
    int head[N],num;
    void ad(int x,int y)
    {
        p[++num]=edge{head[x],y};
        head[x]=num;
    }
    int dfn[N],low[N],mx[N],tim,cut[N],sd[N];
    void taj(int u,int zx)
    {
        int kid=0;
        dfn[u]=low[u]=mx[u]=++tim;
        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                taj(v,zx);
                mx[u]=max(mx[u],mx[v]);
                low[u]=min(low[u],low[v]);
                if(low[v]>=low[u]&&u!=zx) 
                {
                    cut[u]=1;
                    sd[u]=mx[u]-dfn[u];
                }
                if(u==zx) kid++;
            }
            else low[u]=min(low[u],dfn[v]);
        }
        if(u==zx&&kid>1) cut[u]=1; 
    }
    int s=0;
    bool vis[N];
    void dfs(int u,int x)
    {
        vis[u]=1;
        s++;
        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(v==x) continue;
        //    printf("@%d %d
    ",u,v);
            if(!vis[v]) dfs(v,x);
        }
    }
    void print(int x)
    {
        int ans=n-1;
        memset(vis,0,sizeof(vis));
    //    puts("Bear");
    //    for(int i=head[7];i;i=p[i].next) printf("%d ",p[i].to);puts("");
        for(int i=1;i<=n;i++)
        {
            if(i==x) continue;
            if(!vis[i]) 
            {
                s=0;
                dfs(i,x);
                ans+=s*(n-s);
    //            printf("#%d %d
    ",i,s);
            }
        }
        printf("%d
    ",ans);
    //    system("pause");
    }
    int main()
    {
    //    freopen("1.out","w",stdout);
        srand(time(0));
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            ad(x,y);ad(y,x);
        }
        taj(1,1);
        for(int i=1;i<=n;i++)
        {
            if(!cut[i]) printf("%d
    ",2*(n-1));
            else print(i);
        }
        return 0;
    }
    
    //bear Br2 br2 2br BR2 BR nmdp Bear 

    然后我们就t了,我们需要对算法进行优化:

    我们可以在taj的时候就处理出会分出的联通块大小

    我们定义sz[u]为taj搜到u时u子树大小,sm[u]表示u被割后,与根分离的点

    我们思考如果找到一个会u被割v会被影响的点v,那么sz[v]就是割下来连通块大小,同时,n-sm[u]就是u被割下来后,根那一边的大小

    对于根节点,我们直接按上面O(n^2)的做法,O(n)扫一遍

    #include <bits/stdc++.h>
    #define random(a,b) rand()%(b-a+1)+a
    #define int long long
    using namespace std;
    const int N = 1e5+5;
    int n,m;
    struct edge
    {
        int next,to;
    }p[10*N];
    int head[N],num;
    void ad(int x,int y)
    {
        p[++num]=edge{head[x],y};
        head[x]=num;
    }
    int sdkm(int x){return x*(n-x);}
    int dfn[N],low[N],mx[N],tim,cut[N],sd[N],sz[N],sm[N];
    void taj(int u,int zx)
    {
        int kid=0,lst;
        sz[u]=sm[u]=1;
        dfn[u]=low[u]=mx[u]=lst=++tim;
        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                taj(v,zx);
                low[u]=min(low[u],low[v]);
                sz[u]+=sz[v];
                if(low[v]>=dfn[u]&&u!=zx) 
                {
                    mx[u]=max(mx[u],mx[v]);
                    cut[u]=1;
                    sm[u]+=sz[v];
    //                if(u==3) printf("@%d %d
    ",v,sz[v]);
                    sd[u]+=sdkm(sz[v]);
                    lst=mx[u];
                }
                if(u==zx) kid++;
            }
            else low[u]=min(low[u],dfn[v]);
    //        printf("%d %d %d %d
    ",u,v,low[u],dfn[v]);
        }
        if(u==zx&&kid>1) cut[u]=1; 
    }
    int s=0;
    bool vis[N];
    void dfs(int u,int x)
    {
        vis[u]=1;
        s++;
        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(v==x) continue;
        //    printf("@%d %d
    ",u,v);
            if(!vis[v]) dfs(v,x);
        }
    }
    void print(int x)
    {
        int ans=n-1;
        memset(vis,0,sizeof(vis));
    //    puts("Bear");
    //    for(int i=head[7];i;i=p[i].next) printf("%d ",p[i].to);puts("");
        for(int i=1;i<=n;i++)
        {
            if(i==x) continue;
            if(!vis[i]) 
            {
                s=0;
                dfs(i,x);
                ans+=s*(n-s);
    //            printf("#%d %d
    ",i,s);
            }
        }
        printf("%lld
    ",ans);
    //    system("pause");
    }
    signed main()
    {
    //    freopen("1.out","w",stdout);
        srand(time(0));
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%lld%lld",&x,&y);
            ad(x,y);ad(y,x);
        }
        taj(1,1);
        if(cut[1]) print(1);
        else printf("%lld
    ",2*(n-1));
        for(int i=2;i<=n;i++)
        {
            if(!cut[i]) printf("%lld
    ",2*(n-1));
            else 
            {
    //            printf("#%d %d  ",sd[i],sm[i]);
                printf("%lld
    ",sd[i]+sdkm(n-sm[i])+n-1);
            }
        }
        return 0;
    }
    
    //bear Br2 br2 2br BR2 BR nmdp Bear 
  • 相关阅读:
    JSP自定义标签
    Java集合之Arrays 剖析
    关于Java8中的Comparator那些事
    关于Comparable和Comparator那些事
    浅析Thread的join() 方法
    多线程的具体实现
    如何实现 List 集合的线程安全
    集合使用 Iterator 删除元素
    Tomcat目录详解
    一文读懂微服务架构
  • 原文地址:https://www.cnblogs.com/shenbear/p/12228003.html
Copyright © 2011-2022 走看看