• 第一阶段集训(这篇先写写tarjan以及圆方树)


      第一阶段的集训结束了w,不得不说oi太长时间不整是会退步的。

      怎么说好呢,集训这几天过的很充实,知识收货很多,题调的也不少,自己的目标更明确了吧,不过这几天集训也是可以看出蒟蒻就是蒟蒻,还是太菜了。。。。不过会努力的w。

      首先得感谢一下兔哥,假期还来给我们上课,还要饱受牙疼的折磨。。。再次感谢兔哥,%%%%%大神就是大神w。

      结束之后想想这两天都记住什么了w,第一天讲了tarjan,印象最深的还是圆方树这个知识点吧,毕竟这个是个木有学习过的知识。

    算了,要不我还是顺道复习一下强联通分量blabla什么的吧。。。。。。。

      + 强联通分量(不要告诉我博客园不支持markdown)。。。。

      在有向图G中,如果两个顶点u,v之间存在一条路径u到v的路径而且也存在一条v到u的路径,则称这两个顶点u,v是强联通的。如果有向图G的每两个顶点都强联通,称G是一个强联通图。有向非强联通图的极大强联通子图,称之为强联通分量。若将有向图中的强联通分量都缩成一个点,则原图会形成一个DAG。

      + 那么我们一般求强联通分量的方法是tarjan,那么它是怎么实现的w?

      其实,tarjan本质上是个dfs,dfs过程中向栈中push当前节点。当找到一个强联通分量的时候,(其包含的节点一定都在栈中连续排列),so我们把它们全部都pop掉,这些元素构成一个强联通分量。

      每个节点u有两个值——dfn[u]和low[u]。dfn[u]就是dfs序中的位置,low[u]就是u经过最多一条栈中横叉边所能到达的dfn最小的点。这里我们解释一下什么是横叉边,因为我们tarjan是dfs,这棵dfs树上有原图上的边,在dfs树上由父亲指向儿子的叫做树枝边,由父亲指向儿子下面其他后代的叫做前向边,由后代指向祖先的叫做后向边,像其他的边(一个子树中的点指向另一个子树中的点)称之为横叉边。栈中横叉边,顾名思义,就是终点是栈里面元素的边。

      那么问题来了,如何求我们这个所谓的“经过最多一条栈中横叉边能到达的dfn最小的点的dfn”(也就是low)?;

      当我们dfs到的当前点为点u,枚举u能到达的点v。若v没被dfs过,递归dfs点v,并且用low[v]更新low[u](就是low[u] = min(low[u],low[v])),如果v被dfs过且仍在栈中,说明u—>v这条边是一条栈中横叉边,用dfn[v]更新low[u]。

      如果所有v枚举完以后,low[u]仍等于dfn[u],则此时栈中u及u以上的节点同属于一个强联通分量,要一起弹出。

      此处应该有图片。。。。。我也不知道它能不能看到,应该可吧

      然后我们说一说tarjan的性质:

        + 各个强联通分量出栈的顺序是一定的——就是缩点形成的DAG的拓扑序的逆序。

        拓扑序就是DAG中的一种排序,要求任一条边的终点排在起点之后。

        so,tarjan可以求拓扑序,好写???

      tarjan的代码的话。

    void tarjan(int u){
        dfn[u] = low[u] = ++idx;
        stk[+top] = u,ins[u] = 1;
        for(int i = adj[u],v;i;i = nxt[i]){
            if(!dfn[v = go[i]]){
                tarjan(v);
                low[u] = min(low[u],low[v]);
            }
            else if(ins[v]){
                low[u] = min(low[u],low[v]);
            }
        }
        if(low[u] == dfn[u]){
            ncnt++;
            do{
                bel[stk[top]] = ncnt;
                ins[stk[top]] = 0;
            }while(stk[top--] != u);
        }
    }
    

      

      例题讲了挺多w,像联通数(暴力bfs加02爆锤标程),以及迷失在公园里找不到方向(逛公园)

      我记得还讲了无向图的tarjan来着????

      这个好像需要引入几个概念???

        + 割点:将这个点删去后图不连通;

        + 割边: 讲这条边删去后图不连通;

        + 点双联通分量:无割点的极大联通子图。

        + 边双联通分量:无割边的极大联通子图。

      然后如何用tarjan求割边,边双?

        在无向图的dfs树中,原图中的边不可能是横叉边,只可能是前(后)向边或树枝边。所以无向图的tarjan相比有向图很好写,无需讨论某条边是否为栈中横叉边。

        一条无向边(u,v)是割边(dfn[u] < dfn[v])当且仅当它是树枝边,而且low[v] > dfn[u](这时子树v中所有点不能到达子树之外)

        这时我们发现,删去原图中所有割边,剩下的连通块都是边双。

      然后还有tarjan求割点qww

        当u不是dfs树的根的时候,对于树枝边(u,v)(u为v的父亲),如果low[v] >= dfn[u],就是子树v中的点都不通往到子树u之外,那么删去点u,子树v中的点与子树u以外的点不了联通,u为割点。

        但是呢,当u是dfs的根的时候,判断low[v] >= dfn[u]是不够的。因为对于任意的v都满足这个条件啊w,(当子树u以外没有点,上面判断割点的理由就不成立了w)。

        so,我们怎么判断呢?如果u有至少两个子树,则u是割点,只有一个子树,u不是割点。

        还有要值得注意的是,删去所有割点,剩下的联通块并不是点双qww。(这个还挺显然的吧)。

        那么我们怎么求点双呢???

        其中一种求点双的方法是把边push进栈中,当发现low[v] >= dfn[u]时,把边(u,v)及它以上的所有边从栈中弹出,这些边所涉及的点共同形成一个点双(显然包括u)。

        另一种方法是像别的tarjan算法一样把点压入栈中,当发现low[v]  >= dfn[u]时,把v及v以上的点从栈中弹出,再加上u,共同形成一个点双。代码的话。。。。。

        tarjan求割点:

    void tarjan(int u,int pre){
        int v,child_cnt = 0;
        dfn[u] = low[u] ++index;
        for(int i = head[u];i;i = nxt[i]){
            if(!dfn[v = to[i]]){
                tarjan(v,u);
                low[u] = min(low[u],low[v]);
                if(low[v] >= dfn[u]){
                    child_cnt++;
                    iscut[u] = 1;
                }
            else if(v != pre){
                low[u] = min(low[u],dfn[v]);
            }
        }
        if(u == 1 && child_cnt == 1)
            iscut[u] = 0;
    }
    

        求割边,边双代码:

    void tarjan(int u,int pre){
        stk[++top] = u;
        dfn[u] = low[u] = ++idx;
        for(int i = head[u],v;i;i = nxt[i]){
            if(!dfn[v = to[i]]){
                tarjan(v,u);
                low[u] = min(low[u],low[v]);
                if(low[v] > dfn[u]){
                    is_bridge[i] = is_bridge[i ^ 1] = 1;
                    cnt++;
                    do
                        bel[stk[top]] = cnt;
                    while(stk[top--] != v);
                }
            }
             else if(v != pre){
                    low[u] = min(low[u],dfn[u]);
             }
        }
    }
    

      下面终于进入了我们正题了,圆方树!!!!!!!!!!!

      what is it?

        一个无向联通图可以对应一棵圆方树(广义的)。

        圆方树中有圆点和方点。

        圆点是原图中存在的点,方点对应原图的点双。原图中跨点双的边予以保留,点双内部的边删掉,同时所有方点与其对应点双中的圆点连边。

        此处应该有图片:

        

       那么我们怎么建一棵圆方树呢?

        我们新建一个链前,按照定义对每个点双建一个方点,然后把它和点双中的所有点连边,再把原图中跨点双的边也连上。

        显然,可以一边tarjan求点双,一边把边连了。  

    在这里举一道棵题:

      给出一张图,点有点权,每次询问两点之间的简单路径中,权值的最小值最小是多少。n,m,q <=1000000.

      这个题我们把圆方树建出,方点权值为包含圆点权值最小值,然后这个问题就转化成了求链上最小值。可倍增,可树剖。。。。over

  • 相关阅读:
    plink格式数据依据染色体拆分数据、依据染色体合并数据
    Linux系统中date命令
    linux系统 awk 命令依据列字段进行数据匹配
    linux系统中如何将多行数据转化为一行数据
    R语言中sort函数的用法
    各个阶段要读的书
    一个分配固定大小块的内存池(memory pool) 的例子 FROM POCO
    boost tuple笔记(待续)
    万能数据库查询分析器与EXCEL强强联合进行数据分析
    计算机专业毕业生,求职升学,英语自我介绍
  • 原文地址:https://www.cnblogs.com/excellent-zzy/p/11257081.html
Copyright © 2020-2023  润新知