第一阶段集训(这篇先写写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

转载于:https://www.cnblogs.com/excellent-zzy/p/11257081.html

### 回答1: 我可以用C给你写一个Tarjan算法,它是一种用于图算法的深度优先搜索(DFS)算法,可以用来检测图中的强连通分量(SCC),并找出给定图的最小桥(给定两个节点之间的最小边)。具体的实现可以参考下面的代码:// Tarjan算法 // 参数: // g: 图 // v: 从图中获取节点 // adj: 从节点获取邻接顶点 // t: 时间戳 // scc: 强连通分量 // stack: 栈 int tarjan(Graph &g, int v, std::vector<std::vector<int>> &adj, int t, std::vector<std::vector<int>> &scc, std::vector<int> &stack) { // 设置节点v的时间戳 g.timestamp[v] = t; // 设置节点v的最小时间戳 g.min_timestamp[v] = t; // 将节点v加入栈中 stack.push_back(v); // 对节点v的每一个邻接顶点w进行搜索 for (int w : adj[v]) { if (g.timestamp[w] == -1) { // 如果顶点w的时间戳未被设置,则递归搜索 tarjan(g, w, adj, t+1, scc, stack); // 设置节点v的最小时间戳 g.min_timestamp[v] = std::min(g.min_timestamp[v], g.min_timestamp[w]); } else if (std::find(stack.begin(), stack.end(), w) != stack.end()) { // 如果顶点w已经在栈中,则设置节点v的最小时间戳 g.min_timestamp[v] = std::min(g.min_timestamp[v], g.timestamp[w]); } } // 如果节点v的最小时间戳等于其时间戳,说明搜索结束 if (g.min_timestamp[v] == g.timestamp[v]) { // 将节点v以及其之前的节点加入一个强连通分量 std::vector<int> component; int w; do { w = stack.back(); stack.pop_back(); component.push_back(w); } while (w != v); scc.push_back(component); } return 0; } ### 回答2: Tarjan算法是一种用于在有向图中查找强连通分量(Strongly Connected Component,简称SCC)的算法。以下是用C语言实现的Tarjan算法的代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX 100 typedef struct Node { int data; struct Node* next; } Node; typedef struct Stack { int top; int array[MAX_VERTEX]; } Stack; Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->next = NULL; return newNode; } void addEdge(Node* graph[], int src, int dest) { Node* newNode = createNode(dest); newNode->next = graph[src]; graph[src] = newNode; } void DFS(int u, int disc[], int low[], int stackMember[], Stack* stack, Node* graph[], int* count) { static int time = 0; disc[u] = low[u] = ++time; stack->array[++stack->top] = u; stackMember[u] = 1; Node* i; for (i = graph[u]; i != NULL; i = i->next) { int v = i->data; if (disc[v] == -1) { DFS(v, disc, low, stackMember, stack, graph, count); low[u] = (low[u] < low[v]) ? low[u] : low[v]; } else if (stackMember[v] == 1) { low[u] = (low[u] < disc[v]) ? low[u] : disc[v]; } } int popVertex = 0; if (low[u] == disc[u]) { while (stack->array[stack->top] != u) { popVertex = stack->array[stack->top]; printf("%d ", popVertex); stackMember[popVertex] = 0; stack->top--; (*count)++; } popVertex = stack->array[stack->top--]; printf("%d\n", popVertex); stackMember[popVertex] = 0; (*count)++; } } void tarjanSCC(int vertices, Node* graph[]) { int disc[MAX_VERTEX], low[MAX_VERTEX], stackMember[MAX_VERTEX], count = 0; Stack* stack = (Stack*)malloc(sizeof(Stack)); stack->top = -1; for (int i = 0; i < vertices; i++) { disc[i] = -1; low[i] = -1; stackMember[i] = 0; } for (int i = 0; i < vertices; i++) { if (disc[i] == -1) { DFS(i, disc, low, stackMember, stack, graph, &count); } } free(stack); } int main() { int vertices = 8; Node* graph[MAX_VERTEX]; for (int i = 0; i < MAX_VERTEX; i++) { graph[i] = NULL; } addEdge(graph, 0, 1); addEdge(graph, 1, 2); addEdge(graph, 2, 0); addEdge(graph, 2, 3); addEdge(graph, 3, 4); addEdge(graph, 4, 5); addEdge(graph, 5, 3); addEdge(graph, 5, 6); addEdge(graph, 6, 7); printf("Strongly Connected Components:\n"); tarjanSCC(vertices, graph); return 0; } ``` 以上是一个使用C语言编写的Tarjan算法的示例代码。该代码首先定义了节点和栈的数据结构,然后实现了创建节点和添加边的函数。在Tarjan算法函数中,使用DFS进行遍历,并将每个节点的发现时间和最低发现时间进行更新,从而找到强连通分量。最后,打印出所有的强连通分量。 注意:以上代码仅为示例,实际使用中可能需要根据具体情况进行调整和优化。 ### 回答3: Tarjan算法是一种常见的图算法,用于查找图中的强连通分量。以下是一个用C语言写的Tarjan算法的示例: ```c #include <stdio.h> #include <stdlib.h> #define MAX_NODES 100 // 图的最大节点数 int index = 0; // 全局索引计数 int lowlink[MAX_NODES]; // 每个节点在遍历过程中的最低链接值 int visited[MAX_NODES]; // 每个节点是否已被访问 int stack[MAX_NODES]; // 记录遍历过程中访问的节点 int onStack[MAX_NODES]; // 每个节点是否在栈中 int stackIndex = 0; // 栈索引计数 int min(int a, int b) { return a < b ? a : b; } void Tarjan(int node, int graph[MAX_NODES][MAX_NODES]) { visited[node] = 1; // 标记节点已访问 lowlink[node] = index; index++; stack[stackIndex] = node; onStack[node] = 1; // 将节点入栈 stackIndex++; for (int i = 0; i < MAX_NODES; i++) { if (graph[node][i] == 1) { // 有连边到另一节点 if (!visited[i]) { // 未访问过的节点 Tarjan(i, graph); // 递归遍历 lowlink[node] = min(lowlink[node], lowlink[i]); } else if (onStack[i]) { // 已在栈中的节点 lowlink[node] = min(lowlink[node], lowlink[i]); } } } if (lowlink[node] == index - 1) { // 当前节点是强连通分量的根节点 printf("Strongly Connected Component: "); int w; do { w = stack[--stackIndex]; printf("%d ", w); onStack[w] = 0; // 将节点出栈 } while (w != node); printf("\n"); } } int main() { int graph[MAX_NODES][MAX_NODES] = { {0, 1, 0, 0, 0}, {0, 0, 1, 0, 0}, {1, 0, 0, 1, 1}, {0, 0, 0, 0, 0}, {0, 0, 0, 1, 0} }; for (int i = 0; i < MAX_NODES; i++) { visited[i] = 0; onStack[i] = 0; } for (int i = 0; i < MAX_NODES; i++) { if (!visited[i]) { Tarjan(i, graph); } } return 0; } ``` 此示例中,我们定义了一个`Tarjan`函数来进行强连通分量的查找。我们通过输入一个图的邻接矩阵来调用此函数。在`Tarjan`函数中,我们使用一个全局索引计数来记录每个节点在遍历过程中的访问次序,使用`lowlink`数组来记录每个节点在遍历过程中的最低链接值。我们还使用了一个栈来辅助实现遍历过程。在函数中,我们首先标记节点为已访问,并且将其加入栈中,然后遍历与其相邻的节点,继续递归地遍历未访问过的节点。对于已访问的节点,我们通过更新`lowlink`数组来找到其最低链接值。当某个节点的`lowlink`值等于其在遍历过程中的索引值时,该节点以及所有其之前入栈的节点构成一个强连通分量。我们将这些节点出栈,输出强连通分量的内容。最后,在`main`函数中,我们创建一个示例图的邻接矩阵,并调用`Tarjan`函数来查找强连通分量。 请注意,这只是一个简单的示例,并且仅适用于使用邻接矩阵表示的有向图。实际应用中,可能需要根据具体情况进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值