1. 差分约束

1.1. 简介

差分约束算法用于解决如下问题:给出若干形如 \(x_a-x_b\le c\) (均为整数,可以为负数)的不等式,求一组解 \(\{x_i\}\),若不存在解则判断无解。

考虑将原式变形,变为 \(x_a\le x_b+c\)。观察到这与单源最短路里的三角形不等式 \(dis_a\le dis_b+w\) (\(w\) 为节点 \(a,b\) 之间的某边边权)相似。我们使 \(x_i\) 为从源点到节点 \(i\) 的最短路径,对于不等式 \(x_a\le x_b+c\) 我们从节点 \(b\) 向节点 \(a\) 连一条边权为 \(c\) 的有向边。特殊地,我们建立一个源点 \(0\),从源点向所有节点连一条边权为 \(0\) 的有向边,并以源点作为最短路起点。

我们一般采用 SPFA 进行最短路,若图中存在负环,则差分约束系统无解。

特殊地,有些题也可转化为最长路形式进行拓扑排序,因题而异。

1.2. 常见技巧

1.2.1. 常见变形

原式 变形 建边
\(x_a-x_b\le c\) \(x_a\le x_b+c\) add(b,a,c)
\(x_a-x_b\ge c\) \(x_b\le x_a-c\) add(a,b,-c)
\(x_a-x_b<c\) \(x_a\le x_b+c-1\) add(b,a,c-1)
\(x_a-x_b>c\) \(x_b\le x_a-c-1\) add(a,b,-c-1)
\(x_a=x_b\) \(x_a-x_b\le0,x_b-x_a\le0\) add(a,b,0),add(b,a,0)

1.2.2. 简单性质

显著的,将我们得出的一组解 \(x_i\) 整体加减一个常量不影响正确性,因为都消掉了。

1.3. 例题

\(\color{limegreen}{P5960}\)

差分约束模板题。

$\text{Code}$:
#define LL long long
#define UN unsigned
#include<bits/stdc++.h>
using namespace std;
//--------------------//
const int N=1e5+2,M=1e5+2; int n,m;
//----------//
//Edge
struct Edge
{
int to,w,nex;
}edge[M];
int tot,head[N];
void add(int from,int to,int w)
{
edge[++tot].nex=head[from];
head[from]=tot;
edge[tot].to=to;
edge[tot].w=w;
return;
}
//--------------------//
//SPFA
bool vq[N];
int v[N],dis[N];
queue<int>q;
void SPFA(int st)
{
//printf("ST:%d\n",st);
memset(v,0,sizeof(v));
memset(vq,0,sizeof(vq));
memset(dis,0x3f,sizeof(dis));
dis[st]=0;
vq[st]=true;
q.push(st);
while(!q.empty())
{
int now=q.front();
//printf("now:%d\n",now);
q.pop();
vq[now]=false,v[now]++;
if(v[now]>n)
printf("NO"),exit(0);
for(int to,w,i=head[now];i;i=edge[i].nex)
{
to=edge[i].to,w=edge[i].w;
//printf("to:%d %d %d\n",to,dis[to],dis[now]+w);
if(dis[now]+w<dis[to])
{
dis[to]=dis[now]+w;
//printf("%d %d %d\n",dis[to],dis[now],w);
if(!vq[to])
q.push(to),vq[to]=true;
}
}
}
}
//-------------------//
int main()
{
scanf("%d%d",&n,&m);
for(int from,to,w,i=1;i<=m;i++)
{
scanf("%d%d%d",&to,&from,&w);
add(from,to,w);
}
for(int i=1;i<=n;i++)
add(n+1,i,0);
SPFA(n+1);
for(int i=1;i<=n;i++)
{
if(dis[i]==0x3f3f3f3f)
dis[i]=0;
printf("%d ",dis[i]);
}
return 0;
}

\(\color{royalblue}{P3275}\)

将不等式列出之后注意转化、建边细节,这道题用 SPFA 会被卡,需要转化为最长路用拓扑排序求解。

\(\color{blueviolet}{P3530}\)

非常好题目。

一个强连通分量答案固定,多个之间互不影响,考虑一个强连通分量贡献。

考虑此题一个特点,两个点点权相差不超过 \(1\),也就是说强连通分量中点权极值 \(v_x\) 到 \(v_y\) 中每个值都能去到,自然而然答案就是最长路加一,即 \(\min\{dis(i,j) + 1\}\)(\(i,j\) 都为其中的点),Floyd 求解即可。

2. 有向图连通性:强连通分量

2.1. 强连通定义

  • 强连通:对于有向图两点 \(x,y\),若他们互相可达,则称 \(x,y\) 强连通,这种性质为强连通性
  • 强连通图:满足任意两点强连通的有向图称为强连通图
  • 强连通分量:有向图的极大强连通子图称为强连通分量(SCC)

显著的,强连通性具有传递性,并且强连通的两者等价。当在做某些只关心连通性的问题时,一个强连通分量内所有节点等价,便于做题。

2.2. 有向图 DFS 树

遍历每一个节点,若此节点未被访问,则以此节点为根进行 DFS,对于整个图搜索完后可以得到一个有向图 DFS 森林。

对于一棵 DFS 树,主要有以下 \(4\) 种边:

  1. 树边:每次从父节点向子节点进行 DFS 的边。
  2. 返祖边:DFS 时访问到当前节点祖先的边。
  3. 横叉边:DFS 时访问到非当前节点子树内且不是当前节点祖先的点的边。
  4. 前向边:DFS 时访问到当前节点子树内已经访问过的边。

对于两点 \(x,y\),设 \(d_{x,y}=lca(x,y)\),\(fa_i\) 为节点 \(i\) 的父节点,\(T_i\) 为节点 \(i\) 的子树,\(dfn_i\) 为节点 \(i\) 的时间戳。

我们不难发现如下性质:

  • 对于返祖边 \(x\to y\),它使得 \(x\) 到 \(y\) 的 DFS 树上路径上的节点强连通。
  • 对于横叉边 \(x\to y\),有 \(dfn_y<dfn_x\)。
  • 对于前向边 \(x\to y\),删去它并不会影响连通性。

横叉边、前向边均可减小时间戳,而树边和前向边会增大时间戳。由于删去前向边并不会影响连通性,在接下来的证明中,我们忽略前向边的影响。

为了研究横叉边对连通性的影响,我们假设存在节点 \(x,y\) 使得 \(dfn_y<dfn_x\),并且从节点 \(y\) 可达节点 \(x\)。考虑到 \(dfn_y<dfn_x\),而且我们的图中只有树边可以增大时间戳(前向边已经被忽略),所以 \(y\) 到 \(x\) 的路径上一定存在至少一条树边,且一定是通过某一条树边从节点 \(fa_x\) 达到 \(x\)。

所以若 \(y\) 可达 \(x\) 并且 \(dfn_y<dfn_x\),那么 \(y\) 可达 \(fa_x\),那么 \(y\) 一定可达 \(d_{x,y}\)。

相反地,若 \(y\) 可达 \(d_{x,y}\),显然 \(y\) 可达 \(d_{x,y}\)。(别忘了前提 \(dfn_y<dfn_x\)。)

可推出结论:

  • 若 \(dfn_y<dfn_x\),\(y\) 可达 \(x\) 当且仅当 \(y\) 可达 \(d_{x,y}\)。

重新考虑横叉边对连通性的影响,设横叉边 \(x\to y\),利用上述结论,不难发现当且仅当 \(y\) 可达 \(d_{x,y}\) 时,\(x,y\) 强连通。

进一步推出:

  • 若 \(x,y\) 强连通,则 \(x,y\) 路径上的所有点强连通。

2.3. Tarjan 求有向图 SCC

对于每一个 SCC,定义它在 DFS 树上的最浅的那个节点为其“关键点”,我们尝试在一个关键点出求出它所对应的 SCC。显著的,一个关键点有且只有一个对应的 SCC。

对于一个点 \(x\),若它不是关键点,一定有 \(y\in T_x\) 使得有边 \(y\to z\),保证 \(dfn_z<dfn_x\)(这条边是返祖边或者横叉边)。

我们定义 \(low_x\) 为以下节点的 \(dfn\) 最小值:

  • \(T_x\) 中的节点。
  • 一条返祖边 \(y\to z,y\in T_x\),其指向的节点 \(z\)。
  • 一条横叉边 \(y\to z,y\in T_x\),若 \(z\) 可达 \(d_{y,z}\),则包括节点 \(z\)。

若 \(x\) 为关键点,则一定有 \(low_x=x\)(我们把 \(low_x\) 初始化为 \(x\))。比较显著的特性,考虑分类讨论每种边的情况即可,这里省略证明。

我们已知如何寻找关键点,现在考虑求强连通分量。

考虑强连通分量的性质,每个 DFS 树中,强连通分量都是弱连通的(显著)。

根据此性质我们每次找到深度最大的强连通分量,将它与它子树内未被删除的点作为一个强连通分量,然后再删除整个强连通分量。

我们可以用栈维护深搜过的节点,一旦找到整个强连通分量就将其弹出(弹到关键点弹出为止,关键点是深度最小的),使弹出的所有点成为一个 SCC。

现在唯一的问题就是如何求解 \(low\),在回溯时用搜过的直系节点的 \(low,dfn\) 更新当前节点的 \(low\),仍然考虑分类讨论边的种类。

对于现在搜索的到的节点 \(x\),存在一条边 \(x\to y\)。

  • 若其为树边,\(low_x\leftarrow\min\{low_x,low_y\}\)。
  • 若其为返祖边,\(low_x\leftarrow\min\{low_x,dfn_y\}\)。
  • 若其为横叉边,需要判断 \(y\) 是否与 \(x\) 强连通,即判断 \(y\) 是否在栈中,若强连通,\(low_x\leftarrow\min\{low_x,dfn_y\}\)。
  • 若其为前向边,\(low_x\leftarrow\min\{low_x,dfn_y\}\),这并不会改变 \(low_x\) 的值,因为一定有 \(dfn_y>low_x\)。

对于返祖边 \(x\to y\) 来说,\(y\) 一定未出栈,所以在实现时可以采用与横叉边相同的判断方式;对于前向边,如何处理都无所谓。

综上,我们得到了求 \(low\) 的方式。

  • \(low_x\) 初始值设为 \(dfn_x\)。
  • 若当前边 \(x\to y\) 为树边,\(low_x\leftarrow\min\{low_x,low_y\}\)。
  • 若当前边 \(x\to y\) 为非树边,并且 \(y\) 未出栈,\(low_x\leftarrow\min\{low_x,dfn_y\}\)。

2.4. 常用技巧

2.4.1. 缩点

在一定条件下,对于只关心连通性的问题时,一个 SCC 内所有节点等价,因此我们可以将一个 SCC 看做一个点,建出一张新图(一定是一张 DAG),进一步计算。

2.4.2. 优化拓扑排序

对于两个 SCC \(S_1,S_2\),若 \(S_1\) 可达 \(S_2\),则 \(S_1\) 比 \(S_2\) 后出栈。我们按照出栈顺序依次为 SCC 编号,便可以得到 SCC 的拓扑序,所以倒序遍历 SCC,相当于拓扑排序遍历缩点后 DAG,省去了拓扑排序。

2.5. 例题

\(\color{limegreen}{P3387}\)

缩点模板,把 SCC 缩起来后在新图(DAG)上 DP 即可。

\(\color{limegreen}{B3609}\)

强连通分量模板。

\(\color{royalblue}{P3627}\)

简单题,跟板子没啥区别,缩点后 DAG 上 DP。

3. 无向图连通性:双连通分量

3.1. 基本定义

  • 割点:无向图中,删去此节点使得连通分量数量增加,则称此节点为割点
  • 割边:无向图中,删去此边使得连通分量数量增加,则称此节点为割边

连通分量也就是熟悉的连通块。

分量(极大子图):在满足一定条件下,当且仅当 \(\forall G''\) 满足条件使得 \(G'\subsetneq G''\subseteq G\),则称 \(G'\) 为满足此条件的分量

  • 点双连通分量:不存在割点的分量。
  • 边双连通分量:不存在割边的分量。

特殊地,孤立点不是割点,是点双连通图,是边双连通分量;孤立边的端点不是割点,孤立边是割边,是点双连通图,不是边双连通图。

  • 点双连通:若 \(x,y\) 处于同一个点双,则称 \(x,y\) 点双连通。
  • 边双连通:若 \(x,y\) 处于同一个边双,则称 \(x,y\) 边双连通。

3.2. 基本性质

3.2.1. 边双

  1. 考虑一条割边 \((u, v)\),以及其两侧任意两点 \(x, y\),有任意 \(x, y\) 之间路径都包含割边 \((u, v)\)。

  2. 与强连通分量类似,考虑将一个边双中的所有点缩成一个新点,并建出新图,那么此图一定是一棵树,所有树边都是割边,在此性质基础上对于特定题目可以转换为树上问题。

  3. 边双连通具有传递性,若 \(a, b\) 边双连通,且 \(b, c\) 边双连通,则 \(a, c\) 边双连通。

3.2.2. 点双

  1. 考虑一个割点 \(u\),以及其两侧任意两点 \(x, y\),有任意 \(x, y\) 之间路径都包含割点 \(u\)。
  2. 点双交点一定是割点,割点一定是点双的交点。
  3. 点双连通不具有传递性,因为点双可以交于割点。
  4. 一条边恰好属于一个点双。

3.3. Tarjan 求割点

将根节点与非根节点分开考虑,时刻记住是在无向图 DFS 树上进行处理,基于无向图 DFS 树的性质,会对理解有很大帮助。

非根节点

若非根节点 \(x\) 是割点,则其子树内存在点不通过 \(x\) 能到达的所有点均在 \(x\) 子树内,若其不是,那么一定其子树内任意节点可以通过非树边到达某个已经被访问过的点 \(y\),一定有 \(dfn_y < dfn_x\),所以定义 \(low_i\) 表示 \(i\) 子树内的点通过非树边能达到的 \(dfn\) 值最小的一点。\(x\) 是割点当且仅当任意一个子节点 \(u\) 使得 \(low_u < dfn_x\),等价于存在一个子节点 \(u\) 使得 \(low_u \ge dfn_x\)。

根节点

因为是无向图 DFS 树,若根节点 \(x\) 有大于一个子节点,则删去 \(x\) 后各个子树不连通,\(x\) 为割点。

3.4. Tarjan 求割边

仍然考虑在无向图 DFS 树上处理,因为删掉非树边不影响连通性,所以割边一定是树边。

设边 \(e = (u, v)\) 为树边,并且 \(u\) 为 \(v\) 父节点,\(e\) 为割边当且仅当 \(low_v > dfn_u\),这与求割点是类似的。

在求割边时应注意当重边的影响,所以在判断非树边的时候应以边的编号为标准,而不是以端点是否是父节点为标准。

3.5. 例题

\(\color{limegreen}{P8436}\)

边双模板。

\(\color{limegreen}{P8435}\)

点双模板。

\(\color{limegreen}{P3388}\)

割点模板。

\(\color{royalblue}{P2860}\)

首先考虑进行边双缩点,于是得到一棵树,题意转化为给定一棵树,问添加多少边使得整张图变为一个点双。

易证添加的边连叶子是最优策略,考虑这样缩成点双影响到的点是最多的。

先将叶子任意两两匹配,剩余一个的处理方式是简单的,考虑是否存在无解。

对于一条边 \((u, v)\),设其两侧子图分别为 \(U, V\),显然 \(U, V\) 中各有偶数个叶子,否则 \((u, v)\) 一定会被覆盖。

调整方式即拆开一对连在 \(U\) 或 \(V\) 内的匹配,令其跨 \((u, v)\) 相连,这样显然不劣,故一定有解,答案为缩点后叶子个数除以二上取整。

\(\color{royalblue}{P3469}\)

非割点的贡献是简单的,考虑割点答案构成。

去掉割点后在子树内会形成若干连通块,对于一个大小为 \(x\) 的连通块贡献为 \(x (n - x)\),特别的还要考虑不在割点子树的那个连通块,实现是简单的。

「Note」图论方向 - 图论基础的更多相关文章

  1. 「NOTE」常系数齐次线性递推

    要不是考到了,我还没发现这玩意我不是很会-- # 前置 多项式取模: 矩阵快速幂. # 常系数齐次线性递推 描述的是这么一个问题,给定数列 \(c_1,c_2,\dots,c_k\) 以及数列 \(f ...

  2. Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门

      进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...

  3. 「kuangbin带你飞」专题十二 基础DP

    layout: post title: 「kuangbin带你飞」专题十二 基础DP author: "luowentaoaa" catalog: true tags: mathj ...

  4. LibreOJ 2003. 「SDOI2017」新生舞会 基础01分数规划 最大权匹配

    #2003. 「SDOI2017」新生舞会 内存限制:256 MiB时间限制:1500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据   题目描述 ...

  5. 「MoreThanJava」Day 4:面向对象基础

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  6. LOJ #2135. 「ZJOI2015」幻想乡战略游戏(点分树)

    题意 给你一颗 \(n\) 个点的树,每个点的度数不超过 \(20\) ,有 \(q\) 次修改点权的操作. 需要动态维护带权重心,也就是找到一个点 \(v\) 使得 \(\displaystyle ...

  7. LoibreOJ 2042. 「CQOI2016」不同的最小割 最小割树 Gomory-Hu tree

    2042. 「CQOI2016」不同的最小割 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据   题目描述 ...

  8. 「Sqlserver」数据分析师有理由爱Sqlserver之九-无利益关系推荐Sqlserver书单

    在前面系列文章的讲述下,部分读者有兴趣进入Sqlserver的世界的话,笔者不太可能在自媒体的载体上给予全方位的带领,最合适的方式是通过系统的书籍来学习,此篇给大家梳理下笔者曾经看过的自觉不错值得推荐 ...

  9. 「Azure」数据分析师有理由爱Azure之一-Azure能带给我们什么?

    前面我们以相同的方式从数据分析师的视角介绍了Sqlserver,本系列亦同样地延续下去,同样是挖掘数据分析师值得使用的Azure云平台的功能.因云平台功能太多,笔者所接触的面也十分有限,有更专业的读者 ...

  10. 每个程序员都可以「懂」一点 Linux

    提到 Linux,作为程序员来说一定都不陌生.但如果说到「懂」Linux,可能就没有那么多人有把握了.到底用 Linux 离懂 Linux 有多远?如果决定学习 Linux,应该怎么开始?要学到什么程 ...

随机推荐

  1. Web前端入门第 11 问:HTML 常用标签有多少?全量标签有多少?

    HELLO,这里是大熊学习前端开发的入门笔记. 本系列笔记基于 windows 系统. 截止发文,MDN 收录的 HTML 全量标签有 126 个,有 18 个标记已弃用. 名词解释:MDN --- ...

  2. 国产数据库高光时刻!天翼云TeleDB荣登TPC-DS全球测评总榜第二

    近日,天翼云TeleDB数据库以40206063QphDS的吞吐量在国际权威机构TPC(国际事务处理性能委员会)发布的数据库基准测试TPC-DS中荣登全球榜单第二位.中国数据库技术跻身国际顶尖行列,这 ...

  3. AI穿上身:苹果手表如何改变你的生活?

    楔子:一个普通理工男的科技启示录 我是张三,一个标准的90后理工男.在这个日新月异的科技时代,我习惯用精密的逻辑和近乎机械的效率来审视世界.每天早上6点45分准时起床,每一分钟都被精确地规划,生活就像 ...

  4. MySQL 的 JSON 查询

    MySQL 的 JSON 路径格式 MySQL 使用特定的 JSON 路径表达式语法来导航和提取 JSON 文档中的数据 基本结构 MySQL 中的 JSON 路径遵循以下通用格式 $[路径组件] 路 ...

  5. 【网络协议】ANT风格路径匹配

    我们在看java技术书籍的过程中,当加载文件时总会遇到是否支持ant风格路径加载,这里说的ant风格是什么意思呢,今天我查了一下,明白了什么意思,现在总结一下. Ant风格,为请求路径的一种匹配方式. ...

  6. Java实体类如何映射到json数据(驼峰映射到json中的下划线)

    Java实体类(驼峰)映射到json数据(下划线) 由于经常需要接收前端的json数据,而json数据一般都是使用下划线命名的.后端又不太建议使用map接收,所以就需要用到使用自定义类来接收(如果参数 ...

  7. 记一次 .NET某云HIS系统 CPU爆高分析

    一:背景 1. 讲故事 年前有位朋友找到我,说他们的系统会偶发性的CPU爆高,有时候是爆高几十秒,有时候高达一分多钟,自己有一点分析基础,但还是没找到原因,让我帮忙看下怎么回事? 二:CPU爆高分析 ...

  8. Vue的前端项目开发环境搭建

    一.本机window端:安装Node.js,其实质性功能相当于,java的maven https://nodejs.org/en/download/ 二.本机window端:检查Node.js的版本 ...

  9. 为什么 Java 的垃圾收集器将堆分为老年代和新生代?

    为什么 Java 的垃圾收集器将堆分为老年代和新生代? Java 垃圾收集器通过将堆内存划分为 新生代 和 老年代,优化了内存管理,提高了垃圾回收的效率.这种分代思想是基于 对象生命周期的特点. 1. ...

  10. 结合laravel深入理解php的服务容器和依赖注入

    原文:laravel 学习笔记 -- 神奇的服务容器 容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器.一个容器能够装什么,全部取决于你对该容器的定义.当然,有这样一种容器,它存 ...