zoukankan      html  css  js  c++  java
  • 对tarjan的一些理解

    之前做tarjan的题,我一直没有搞清楚有向图和无向图中,代码的不同,今天下午向虎哥和zxk讨论了快一个小时,现在终于清楚些了。

    最基本的一些东西

    有向图

    我们需要求的是强连通分量,在有向图中,有四种边。


    一种边为树枝边,从根节点遍历,每个节点第一次被访问到,即边(x,y)是从x到y是对y的第一次访问。这些边为树边,绿色表示
    一种边为前向边,边(x,y)可以为表示x是y的祖先。蓝色表示。这种边对求scc没有影响,因为搜索树本来就存在从x到y的路径
    一种边为后向边,边(x,y)可以表示y是x的祖先。黄色表示。这种边有用,这条边可以和搜索树上的从y到x的路径构成一个scc
    一种边为横叉边,可以理解为连接到两个兄弟、堂兄弟等之间,二者不像前三个有祖先后裔的关系。红色表示。如果存在一条边可以从y到x的祖先,那么可以构成一个scc,否则这条边没用。
    由于横叉边的存在,我们必须要判断当前点是否在栈内,如果在栈里说明v是u的父亲或祖先,不在栈里说明u和v属于不同分支。有可能我们从u通过横叉边访问到v,但是并不一定从v访问到u的祖先,也就是说虽然u和v通过一条边相连,但它们并不属于一个scc。如果是无向图,那肯定可以从v访问到u。

    void Tarjan(int rt){
    	dfn[rt] = low[rt] = ++dfn_cnt;
    	ins[rt] = 1;
    	for (int i = head[rt]; i; i = edge[i].next){
    		int v = edge[i].to;
    		if (!dfn[v]){
    			Tarjan(v);
    			low[rt] = min(low[rt], low[v]);
    		}
    		else if(ins[v]){//注意这里需要判断是否在栈内
    			low[rt] = min(low[rt], dfn[v]);
    		}
    	}
    	if (dfn[rt] == low[rt]){
    		tot++;
    		while (1){
    			int cur = stk[top--];
    			belong[cur] = tot;
    			ins[cur] = 0;//记得取消标记
    			scc[tot].push_back(cur);
    			if (cur == rt) break;
    		}
    	}
    }
    

    无向图

    我们需要求的是割点,点双;桥,边双。
    有向图有横叉边,只能从u访问到不同分支的v,但不能从v访问到u
    无向图没有横叉边,如果u能访问到v,那v肯定能访问到u
    所以不需要判断是不是在栈内
    
    void tarjan(int rt){
      dfn[rt] = low[rt] = ++dfn_cnt;
      int ch = 0, sum = 0;
      for (int i = head[rt]; i; i = edge[i].next){
      	int v = edge[i].to;
      	if (!dfn[v]){
      		Tarjan(v);
      		low[rt] = min(low[rt], low[v]);
      		ch++;
      		if ((rt == root && ch >= 2) || (rt != root && low[v] >= dfn[u]))
      			cut[rt] = 1;
      	}
      	else low[rt] = min(low[rt], dfn[v]);//不需要判断是否在栈内
      }
    }
    
    

    一道题

    题目描述

    Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 所有towns连通。

    输入格式

    输入n<=100000 m<=500000及m条边

    输出格式

    输出n个数,代表如果把第i个点去掉,将有多少对点不能互通。

    样例输入

    5 5
    1 2
    2 3
    1 3
    3 4
    4 5

    样例输出

    8
    8
    16
    14
    8
    对于一个非割点,删去会多出来2 * (n-1)对
    对于一个割点,删去会有三部分,一是该点本身,二是该点的子树中可能会分成几部分,三是除去以该点为子树的所有节点后剩余部分,每部分相乘再相加即可。
    通过这个题我发现对于一个割点u,并不是把它删去后,它所有子节点v都会满足dfn[u]<=low[v],只是一部分子节点会分开,还有一部分仍然属于一个点双。

    
    
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5 + 5;
    const int maxm = 5e5 + 5;
    typedef long long ll;
    struct Edge{
    	int to, next;
    }edge[maxm << 1];
    int n, m, cnt, tot, dfn_cnt, head[maxn], dfn[maxn], low[maxn], ins[maxn], belong[maxn];
    int num[maxn];
    int in[maxn];
    ll ans[maxn];
    ll size[maxn], cut[maxn], root;
    void Add(int u, int v){
    	edge[++cnt].to = v;
    	edge[cnt].next = head[u];
    	head[u] = cnt;
    }
    void Tarjan(int rt){
    	dfn[rt] = low[rt] = ++dfn_cnt;
    	size[rt] = 1;
    	int ch = 0, sum = 0;
    	for (int i = head[rt]; i; i = edge[i].next){
    		int v = edge[i].to;
    		if (!dfn[v]){
    			Tarjan(v);
    			size[rt] += size[v];
    			low[rt] = min(low[rt], low[v]);
    			if (low[v] >= dfn[rt]){
    				ch++;
    				ans[rt] += (ll)size[v] * (n - size[v]);
    				sum += size[v];
    				if (rt != -1 || ch > 1) cut[rt] = 1;
    			}
    		}
    		else low[rt] = min(low[rt], dfn[v]);
    	}
    	if (cut[rt]) ans[rt] += n - 1 + (ll)(n - sum - 1) * (sum + 1);
    	else ans[rt] += (n - 1) * 2;
    }
    int main(){
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= m; i++){
    		int u, v;
    		scanf("%d%d", &u, &v);
    		if (u == v) continue;
    		Add(u, v);
    		Add(v, u);
    	}
    	Tarjan(1);
    	for (int i = 1; i <= n; i++)
    		cout << ans[i] << endl;
    }
    
    
  • 相关阅读:
    Jenkins 插件管理
    持续集成 目录
    gitlab 目录
    jenkins 目录
    POJ 2828
    POJ 2782
    POJ 2725
    POJ 2769
    POJ 2739
    POJ 2707
  • 原文地址:https://www.cnblogs.com/ghosh/p/13189487.html
Copyright © 2011-2022 走看看