• Tarjan 连通性


    Tarjan 连通性

    Tarjan 爷爷的代表作,图的连通性问题直接解决

    两个核心数组:

    1. \(dfn_u\)\(u\) 的 dfs 序
    2. \(low_u\)\(u\)\(u\) 的后代通过返祖边能回到的最小的 \(dfn\)

    四种边

    • 树边:dfs 搜索树中的边
    • 返祖边:若在搜索树中, \(i\)\(j\) 的祖先,则原图中从 \(j\)\(i\) 的边是返祖边
    • 前向边:若在搜索树中, \(i\)\(j\) 的后代,则原图中从 \(j\)\(i\) 的边是前向边
    • 交叉边:若在搜索树中, \(i\)\(j\) 不在同一子树,则从 \(i\)\(j\) 的边是交叉边

    前向边、交叉边只存在于有向图

    核心代码

    int dfn[N],low[N],clk;
    void tarjan(int u,int fa) {
        dfn[u]=low[u]=++clk;
        for(int i=lst[u],v;i;i=nxt[i]) {
            if(!dfn[v=to[i]]) {
                tarjan(v,u);
                low[u]=min(low[u],low[v]);
            } else if(这是返祖边)
                low[u]=min(low[u],dfn[v]);
        }
    }
    int main() {
        // Input ....
        for(int i=1;i<=n;i++)
            if(!dfn[i])tarjan(i,-1);
    }
    

    无向图

    无向图的操作与连通分量有关

    • 连通分量:无向图的极大连通子图

    割点

    • 定义:删去一个点使得图中联通分量增加,该点叫做割点

    • 求法:如果 \(u\) 存在一个儿子 \(v\)\(low_v\ge dfn_u\)

      说明删去 \(u\)\(v\) 不能通过返祖边连回 \(u\) 的祖先, \(u\) 是割点

    • 特判:如果 \(u\) 是搜索树的根且只有 1 个儿子,则 \(u\) 不是割点

    割边(桥)

    • 定义:删去一条边使得图中联通分量增加,该点叫做割边(桥)

    • 求法:如果对于一条边 \((u,v)\)\(low_v>dfn_u\)

      说明删去该边后 \(v\) 不能通过返祖边连回 \(u\)\(u\) 的祖先,该边是割边(桥)

    割点 & 桥 的实现

    int cut[N],br[M<<1];
    void tarjan(int u,int fa) {
        dfn[u]=low[u]=++clk;
        register int ch=0;
        for(int i=lst[u],v;i;i=nxt[i]) {
            if(!dfn[v=to[i]]) {
    			++ch;
    			tarjan(v,u);
    			if(low[v]>=dfn[u])cut[u]=1;
    			if(low[v]>dfn[u])br[i]=br[i^1]=1;
    			low[u]=min(low[u],low[v]);
            } else if(v^fa)
                low[u]=min(low[u],dfn[v]);
        }
        if(fa==-1 && ch==1)cut[u]=0;
    }
    

    点双连通分量

    • 定义:无向图的极大无割点子图 ,简称点双

    • 求法:一个点(如割点)可能在多个点双中,而一条边只能在一个点双中

      考虑用栈存下边,当 \(u\) 是割点时,一直弹栈直到弹出边 \((u,v)\),弹出的边组成点双

    vector<int>bs[N];
    int id[N],tot;
    void tarjan(int u,int fa) {
        dfn[u]=low[u]=++clk;
        for(int i=lst[u],v,nw;i;i=nxt[i]) {
            if(!dfn[v=to[i]]) {
                s[++top]=i;
                tarjan(v,u);
                low[u]=min(low[u],low[v]);
                if(low[v]>=dfn[u]) {
                    tot++;
                    while(1) {
                        nw=s[top--];
                        if(id[to[nw]]!=tot)
                            id[to[nw]]=tot,bs[tot].pb(to[nw]);
                        if(id[to[nw^1]]!=tot)
                            id[to[nw^1]]=tot,bs[tot].pb(to[nw^1]);
                        if(to[nw]==v && to[nw^1]==u)break;
                    }
                }
            } else if(v!=fa && dfn[v]<dfn[u]) {
                low[u]=min(low[u],dfn[v]);
                s[++top]=i;
            }
        }
    }
    

    边双连通分量

    • 定义:无向图的极大无桥子图,简称边双
    • 求法:可用点双的思想,把点压栈,然后遇到桥弹点到弹出 \(u\) ,弹出的点组成边双
    • 求法 2:更简单。第一次 \(dfs\) 求出桥,第二次 \(dfs\) 打标记,遇到桥就编号加 1 ,编号相同的点构成边双
    void tarjan(int u,int fa) {
        dfn[u]=low[u]=++clk;
        for(int i=lst[u],v;i;i=nxt[i]) {
            if(!dfn[v=to[i]]) {
    			++ch;
    			tarjan(v,u);
    			if(low[v]>dfn[u])br[i]=br[i^1]=1;
    			low[u]=min(low[u],low[v]);
            } else if(v^fa)
                low[u]=min(low[u],dfn[v]);
        }
    }
    int tot;
    void dfs(int u,int fa,int nw) {
        cl[u]=nw;
        for(int i=lst[u];i;i=nxt[i])
            if((v=to[i])^fa) {
                if(br[i])++tot,dfs(v,u,tot);
                else dfs(v,u,nw);
            }
    }
    

    有向图

    强连通分量

    有向图中 tarjan 用于求强连通分量

    • 强连通图:一个任意两点都可以相互到达的有向图
    • 强连通分量:一个有向图的极大的强连通子图

    求法:将点入栈,遇到 \(dfn_u=low_u\) 时就弹栈顶直到弹出 \(u\) ,弹出的点在一个强连通分量

    \(low_u<dfn_u\) 时说明强连通分量还可以扩大,所以只能 \(dfn_u=low_u\)

    int sz[N],cl[N],tot;
    void tarjan(int u) {
        dfn[u]=low[u]=++clk,s[++top]=u;
        for(int i=lst1[u],v;i;i=nxt1[i]) {
            if(!dfn[v=to1[i]]) {
                tarjan(v);
                low[u]=min(low[u],low[v]);
            } else if(!cl[v]) {
                low[u]=min(low[u],dfn[v]);
            }
        }
        if(low[u]==dfn[u]) {
            ++tot,sz[tot]=1;
            while(s[top]!=u)
                ++sz[tot],cl[s[top]]=tot,--top;
            cl[u]=tot,--top;
        }
    }
    

    缩点

    • 作用:将强连通分量看成一个点,可以将原图缩成一个 DAG

      可以用于方便地做 dp

    • 做法:求强连通分量给点染色,然后枚举一个每条边 \((u,v)\)

      如果 \(color_u\ne color_v\) 就从 \(color_u\)\(color_v\) 连边

    for(int i=1;i<=n;i++)
    	for(int j=lst[i];j;j=nxt[j])
    		if(cl[i]!=cl[to[j]])
    			Ae2(cl[i],cl[to[j]]);
    

    [HAOI2010]软件安装

    缩点后一定是树,在 \(\text{dfs}\) 序上 dp

    \(f_{i, j}\) 为做到了 \(dfs\) 序为 \(i\) 的点用了空间为 \(j\) 的最大价值

    \(\text{dfs}\) 序为 \(i\) 的点是 \(u\)\(w\) 是所用空间,\(v\) 是价值

    刷表,对于 \(i\) 选或不选讨论

    \(f_{i+1,j+w_u} = \max(f_{i,j}+v_i)\)

    \(f_{i+sz_u,j}=\max(f_{i,j})\)

    处理依赖:从根到该点路径上(不包括该店)的 \(w\) 之和为必须要的空间

    #include <bits/stdc++.h>
    using namespace std;
    typedef unsigned long long uLL;
    typedef long double LD;
    typedef long long LL;
    typedef double db;
    const int N = 105;
    int n, m, a[N], Ecnt1, lst1[N], b[N], clk;
    int dfn[N], st[N], top, cl[N], low[N], tot;
    int w[N], v[N], lst2[N], Ecnt2;
    int rk[N], sz[N], pre[N], rd[N], f[N][505];
    struct Ed { int to, nxt; } e1[N], e2[N];
    inline void cmx(int &x, int y) { x < y ? x = y : 1; }
    inline void Ae1(int fr, int go) { e1[++Ecnt1] = (Ed){ go, lst1[fr] }, lst1[fr] = Ecnt1; }
    inline void Ae2(int fr, int go) { e2[++Ecnt2] = (Ed){ go, lst2[fr] }, lst2[fr] = Ecnt2; }
    void tarjan(int u) {
        dfn[u] = low[u] = ++clk, st[++top] = u;
        for (int i = lst1[u], v; i; i = e1[i].nxt) {
            if (!dfn[v = e1[i].to]) {
                tarjan(v), low[u] = min(low[u], low[v]);
            } else if (!cl[v]) low[u] = min(low[u], dfn[v]);
        }
        if (low[u] == dfn[u]) {
            register int o;
            ++tot;
            while (st[top] ^ u)
                o = st[top--], cl[o] = tot, w[tot] += a[o], v[tot] += b[o];
            cl[u] = tot, w[tot] += a[u], v[tot] += b[u], --top;
        }
    }
    void dfs(int u) {
        rk[++clk] = u, sz[u] = 1;
        for (int i = lst2[u], v; i; i = e2[i].nxt)
            pre[v = e2[i].to] = pre[u] + w[u], dfs(v), sz[u] += sz[v];
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
        for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
        for (int i = 1, u; i <= n; i++) {
            scanf("%d", &u);
            if (u) Ae1(u, i);
        }
        for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
        for (int i = 1; i <= n; i++)
            for (int j = lst1[i], v; j; j = e1[j].nxt)
                if (cl[i] ^ cl[v = e1[j].to]) ++rd[cl[v]], Ae2(cl[i], cl[v]);
        for (int i = 1; i <= tot; i++) if (!rd[i]) Ae2(0, i);
        clk = 0, dfs(0);
        for (int i = 1, u; i <= clk; i++) {
            u = rk[i];
            for (int j = pre[u]; j <= m; j++) {
                if (j + w[u] <= m) cmx(f[i + 1][j + w[u]], f[i][j] + v[u]);
                cmx(f[i + sz[u]][j], f[i][j]);
            }
        }
        printf("%d", f[clk + 1][m]);
    }
    
  • 相关阅读:

    python 爬取可用
    安装完出现Deprecated: Function ereg_replace() is deprecated in
    mysql数据库还原出错ERROR:Unknown command ‘\’解决手记
    mysql 常用语句
    This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery 解决方法
    js 中 json对象 与 json字符串 间相互转换
    神器 Sublime Text 3 的一些常用快捷键
    神器 Sublime Text 3 的一些常用插件
    apache php gzip压缩输出的实现方法
  • 原文地址:https://www.cnblogs.com/KonjakLAF/p/15485535.html
Copyright © 2020-2023  润新知