圆方树学习笔记

双连通分量

定义

对于无向图中两点\(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]铁人两项

posted @ 2022-03-16 17:13  xzj213  阅读(52)  评论(0编辑  收藏  举报