• 圆方树学习笔记


    双连通分量

    定义

    对于无向图中两点\(u,v\),若无论删去哪条边都不能使其不连通,则称\(u,v\)边双连通

    对于无向图中两点\(u,v\),若无论删去哪个点都不能使其不连通,则称\(u,v\)点双连通

    边双连通

    我们发现使两点不连通的边其实就是桥(割边)

    所以我们只需要把桥删了即可

    void tarjan(int now,int fa) {
        dfn[now]=low[now]=++cnt;
        for (int i=beg[now];i;i=e[i].nex) {
            int nex=e[i].to;
            if (nex==fa) continue;
            if (!dfn[nex]) {
                tarjan(nex,now);
                chkmin(low[now],low[nex]);
                if (dfn[now]<low[nex]) e[i^1].cut=e[i].cut=1;
            }
            else chkmin(low[now],dfn[nex]);
        }
    }
    void dfs(int now,int fa,int C) {
        col[C].push_back(now);
        bel[now]=C;
        for (int i=beg[now];i;i=e[i].nex) {
            int nex=e[i].to;
            if (nex==fa || bel[nex] || e[i].cut) continue;
            dfs(nex,now,C);
        }
    }
    

    点双连通

    我们发现,两个点双一定有一个割点连接,且这个割点被两个点双包含,所以我们可以直接根据强连通分量的方式,把点入栈,然后找到割点就不停弹出点,并全部存入点双

    void tarjan(int now,int Fa) {
        dfn[now]=low[now]=++cnt;st[++top]=now;
        if (now==Fa && !beg[now])  col[++tot].push_back(now);
        for (int i=beg[now];i;i=e[i].nex) {
            int nex=e[i].to;
            if (nex==Fa) continue;
            if (!dfn[nex]) {
                tarjan(nex,now);
                chkmin(low[now],low[nex]);
                if (dfn[now]<=low[now]) {
                    ++tot;
                    while(1) {
                        col[++tot].push_back(st[top]);
                        if (st[top]==nex) {top--;break;}
                        top--;
                    }
                    col[tot].push_back(now);
                }
            }
            else chkmin(low[now],dfn[nex]);
        }
    }
    

    圆方树

    圆方树主要用于处理仙人掌图(每条边在不超过一个简单环中的无向图)的问题

    在刚刚的点双基础上,我们来构建一棵圆方树

    我们将原来的点看做一个圆点,对于一个点双建一个方点,方点向该点双里的每个圆点连边

    那么,每个点双就是一个菊花,然后整张图是由割点连接很多菊花

    当原图是一个联通图时,我们构建的图就是一棵树

    然后,我们考虑如何实现这个构建过程

    由于圆方树基于点双,所以在求点双的过程中可以建出这棵树

    void tarjan(int now,int fa) {
        dfn[now]=low[now]=fa;
        if (now==fa && !beg[now]) {
            cntcol++;
            E[cntcol].push_back(now);
            E[now].push_back(cntcol);
        }
        st[++top]=now;
        for (int i=beg[now];i;i=e[i].nex) {
            int nex=e[i].to;
            if (nex==fa) continue;
            if (!dfn[nex]) {
                tarjan(nex,now);
                chkmin(low[now],low[nex]);
                if (low[nex]<=dfn[now]) {
                    cntcol++;
                    while(1) {
                        E[cntcol].push_back(st[top]);
                        E[st[top]].push_back(cntcol);
                        if (st[top]==nex) {top--;break;}
                        top--;
                    }
                    E[cntcol].push_back(now);
                    E[now].push_back(cntcol);
                }
                else chkmin(low[now],dfn[nex]);
            }
        }
    }
    

    性质

    对于一个点双中的两个点,他们之间简单路径的并集,恰好等于这个点双

    考虑如何证明这个性质

    首先,一条路径如果出了点双,一定不能再回来,否则违背了点双定义

    基于此点,我们只需要证明一个点双中任意三个不同点\(x,y,z\),一定存在一条路径可以从\(x\),经过\(y\),到达\(z\)

    考虑网络流

    从源点向\(y\)建一条流量为\(2\)的边

    \(x,z\)向汇点连一条流量为\(1\)的边

    对于图中的无向边就建两条流量为\(1\)的边

    然后,因为每个点只能经过一次,所以我们要限制每个点只能流\(1\)

    所以我们拆点,对于点\(i\)\(i'\)连一条流量为\(1\)的边

    那么如果最后的最大流为\(2\),就说明一定经过了\(y\)

    根据最大流最小割定理,显然最小割时小于\(2\)的,所以我们要证明最小割大于\(1\)

    这个证明等价于网络流中割掉任何一个点无法使图不连通

    分类讨论一下

    如果删掉\(x,z\)向汇点的边,根据点双定义,一定存在一条路径从\(y\)连向另一个每删边的点

    如果删掉一个点拆点的边,因为点双是没有割点的图,所以剩下的图一定联通

    如果删掉原图中的边,这跟点双定义,也是联通的

    所以,最大流等于\(2\)

    所以证毕

    例题

    [APIO2018]铁人两项

  • 相关阅读:
    修改了/etc/fstab之后出现登录密码输入之后又返回登录界面的问题
    设计模式 之 享元模式
    设计模式 之 组合模式
    设计模式 之 桥接模式
    设计模式 之 外观模式
    设计模式 之 代理模式
    T-SQL 理解SQL SERVER中的分区表(转)
    C# 读取文件中的sql语句 创建数据库以及表结构
    java Web 监听器Listener详解
    java Web 过滤器Filter详解
  • 原文地址:https://www.cnblogs.com/xzj213/p/16013586.html
Copyright © 2020-2023  润新知