更佳的阅读体验:2023 国庆集训 Day 6A 笔记,本文同步发表于 洛谷博客

以下内容为个人总结,如有错误或您有不同见解,欢迎指出。


割点 / 割边

在无向图中讨论。

删掉这个点 / 边,图变得不连通。

Tarjan 求割点

  • 随便找个根节点,跑 DFS,记录 DFS 访问的时间戳 $dfn$。
  • 对于根节点,我们判断其是否是割点,只需判断是否有大于 $1$ 个子树。
  • 考虑非根节点 :

    • 考虑非树边长什么样子:一定是从祖先到子孙。
    • 那么假设 $x$ 不是割点,那么子树内的每个点都能到 $x$ 的上面部分。
    • 设 $low[x]$ 表示 $x$ 子树内,通过非树边走到的最浅是哪里。
    • 如果存在 $x$ 的儿子 $y$ 满足 $low[y] \ge dfn[x]$,那么 $x$ 就是割点。
int dfn[N], low[N], cut[N], tmp, num, cnt, rt;

void dfs(int x, int fa) {
    dfn[x] = low[x] = ++num;
    for (int i = h[x]; i; i = ne[i]) {
        int y = to[i];
        if (y == fa) continue;
        if (!dfn[y]) {
            dfs(y, i ^ 1), low[x] = min(low[x], low[y]);
            if (x != rt && low[y] >= dfn[x]) cut[x] = 1;
            if (x == rt) tmp++;
        } else low[x] = min(low[x], dfn[y]);
    }
}

缩点

参考材料

stk[++st] = u;
for (int i = point[u]; i != -1; i = edge[i].nxt) {
    int v = edge[i].v;
    if (!dfn[v]) {
        Tarjan(v);
        low[u] = min (low[u], low[v]);
        // 因为v在u的子树内,所以low[v]可以用于更新low[u]
        if (low[v] >= dfn[u]) {  // 子树中没有可以连到父亲上面的边
            cut[u] = 1;
            dccn++;
            dcc[dccn].clear();
            dcc[dccn].push_back(u);  // u是割点,可能包含在多个点双中,不能弹出
            while (true) {
                int w = stk[st];
                dcc[dccn].push_back(w);
                st--;
                if (w == v)  // 做到子树全部弹出为止,不然v的兄弟也会被弹出
                    break;
            }
        }
    }
}

Tarjan 求割边

void tarjan(int x, int fa) {
    low[x] = dfn[x] = ++num;
    vis[x] = true, st[++top] = x;
    int cnt = 0;
    for (int i = h[x]; i; i = ne[i]) {
        int y = to[i];
        if (y == fa && (++cnt) < 2) continue;
        if (!dfn[y]) tarjan(y, x), low[x] = min(low[x], low[y]);
        else low[x] = min(low[x], dfn[y]);
    } if (dfn[x] == low[x] && ++sc)
        while (z = st[top--]) {
            col[z] = sc, vis[z] = false;
            if (z == x) break;
        }
}

强连通分量

在有向图中,如果两个点 $u$,$v$ 满足同时存在从 $u$ 到 $v$ 和从 $v$ 到 $u$ 的路径,则称两个点强连通。

如果有向图任意两个点强连通,则称为强连通图。有向图的极大强连通子图称为强连通分量。

注意到强连通关系是传递的,所以有向图可以划分为若干不交的强连通分量。

在有向图的 DFS 树中,有 树边非树边(没用)返祖边(有用)横叉边(部分有用) 的四种边。

  • DFS 到 x 时,我们用栈维护 $x$ 的每个祖先目前的强连通分量。
  • 遍历所有 $x$ 的边 $(x, y)$:

    • 如果 $y$ 未被遍历过,则 $dfs(y)$。
    • 如果 $y$ 被遍历过,如果 $y$ 在某个祖先的强连通分量中,则形成了环,将若干连通分量合并,否则不做更新。
  • 合并操作可以通过记录 $low$ 数组实现。
int sta[N], num;
bool vis[N];

void tarjan(int x) {
    low[x] = dfn[x] = ++num;
    sta[++top] = x, vis[x] = true;
    for (int i = h[x]; i; i = ne[i]) {
        int y = to[i];
        if (!dfn[y]) {
            tarjan(y);
            low[x] = min(low[x], low[y]);
        } else if (vis[y]) low[x] = min(low[x], low[y]);
    } if (dfn[x] == low[x]) {
        int y;
        while (y = sta[top--]) {
            sd[y] = x;
            vis[y] = false;
            if (x == y) break;
            w[x] += w[y];
        }
    }
}

洛谷 P3469 - [POI2008] BLO-Blockade

  • 魔改一下 Tarjan 求割点的过程。
  • 得到 $sum - \sum_{low[y] \ge dfn[x]} si[y]$,$ans[x] = (n - sum - 1) \times (sum + 1) + \sum_{low[y] \ge dfn[x]} si[y](n - si[y])$。

洛谷 P1407 - [国家集训队] 稳定婚姻

  • 婚姻让女方向男方连边,旧情让男方向女方连边。不安全的婚姻说明出现了环,也就是在一个连通分量内。
  • 所以跑 Tarjan,一对夫妻在强连通分量里面说明不安全。

UOJ 67 - 新年的毒瘤

  • 只需满足两个条件:

    • 删掉的点不能是割点。
    • 删掉这个点以后满足点数等于边数 $+ 1$。

洛谷 P4652 - [CEOI2017] One-Way Streets

  • 因为保证有解。我们考虑原图中边双里面的边无法确定方向,所以考虑缩边双。
  • 缩完以后,现在相当于有一棵树的原问题。
  • 可以树上差分,比如从 $x$ 到 $y$,让 $d[x]$ 加 $1$,$d[y]$ 减 $1$,这样边权为正代表向上,边权为负代表向下。

CF402E - Strictly Positive Matrix

  • 矩阵乘法转化为计算路径的条数。
  • 即询问是否存在 $k$ 使得所有 $i$ 到 $j$ 都有长度为 $k$ 的路径。
  • 那么需要所有人都在一个强连通分量里面,注意存在某个点有自环,所以可以凑出长度为 $k$ 的路径。

洛谷 P3225 - [HNOI2012] 矿场搭建

  • 将点双缩点变成一棵树,那么叶子节点一定要建,非叶子节点可以不用建。
  • 单独讨论整个图是一个点双的情况。

二分图

  • 二分图最大匹配:一定存在增广路,扩展所有增广路即可。
  • 二分图最小点覆盖:左侧为匹配点开始 DFS,答案为左侧未 DFS 到的点 $+$ 右侧 DFS 到的点。
  • 二分图最大独立集:最小点覆盖的补集。

洛谷 P1155 - [NOIP2008 提高组] 双栈排序

  • 当只有一个栈的时候,怎样是合法的?
  • 如果存在 $i < j < k$,$a_k < p_i < p_j$ 不合法。
  • 对于两个栈的情况,我们找到所有不合法的 ,这两个人不能放在一起,想到二分图染色。
  • 找到字典序最小的合法解。

洛谷 P2055 - [ZJOI2009] 假期的宿舍

洛谷 P4304 - [TJOI2013] 攻击装置

  • 对棋盘黑白染色,有攻击关系建边。
  • 那么就是二分图最大独立集 $= n -$ 最大匹配。

洛谷 P1129 - [ZJOI2007] 矩阵游戏

  • 行和列都建点跑二分图匹配。

洛谷 P1263 - [CEOI2002] Royal guards

  • 将连续的一段空地而不是一整行建成一个点,类似矩阵游戏的连边。

基环树

洛谷 P1453 - 城市环路

  • 对于树来说,发现就是求最大独立集,设 $f[x][0 / 1]$ 表示 $x$ 点选或不选的最大值。
  • 对于环来说,考虑断环为链,相邻两个点一定有一个是不选,分别枚举强制某个点不选做一次链上的 DP 即可。

洛谷 P4381 - [IOI2008] Island

  • 考虑求一个基环树的直径,先求出树的直径。
  • 对于环上的每个点,我们考虑记录 $f[x]$ 表示从 $x$ 开始到其子树内最长路径是多少。
  • 断环为链,记录链上的路径前缀和,那么答案就是 $\max(f_i + f_j + s_j - s_i)$,$\max(f_i + f_j + len - (pre_j - pre_j))$,代表走环的两部分。
  • 直接扫一遍即可。

洛谷 P5049 - [NOIP2018 提高组] 旅行 加强版

最后修改:2024 年 02 月 14 日
如果觉得我的文章对你有用,请随意赞赏