「Note」图论方向 - 图论基础
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\) 种边:
- 树边:每次从父节点向子节点进行 DFS 的边。
- 返祖边:DFS 时访问到当前节点祖先的边。
- 横叉边:DFS 时访问到非当前节点子树内且不是当前节点祖先的点的边。
- 前向边: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. 边双
考虑一条割边 \((u, v)\),以及其两侧任意两点 \(x, y\),有任意 \(x, y\) 之间路径都包含割边 \((u, v)\)。
与强连通分量类似,考虑将一个边双中的所有点缩成一个新点,并建出新图,那么此图一定是一棵树,所有树边都是割边,在此性质基础上对于特定题目可以转换为树上问题。
边双连通具有传递性,若 \(a, b\) 边双连通,且 \(b, c\) 边双连通,则 \(a, c\) 边双连通。
3.2.2. 点双
- 考虑一个割点 \(u\),以及其两侧任意两点 \(x, y\),有任意 \(x, y\) 之间路径都包含割点 \(u\)。
- 点双交点一定是割点,割点一定是点双的交点。
- 点双连通不具有传递性,因为点双可以交于割点。
- 一条边恰好属于一个点双。
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. 例题
边双模板。
点双模板。
割点模板。
首先考虑进行边双缩点,于是得到一棵树,题意转化为给定一棵树,问添加多少边使得整张图变为一个点双。
易证添加的边连叶子是最优策略,考虑这样缩成点双影响到的点是最多的。
先将叶子任意两两匹配,剩余一个的处理方式是简单的,考虑是否存在无解。
对于一条边 \((u, v)\),设其两侧子图分别为 \(U, V\),显然 \(U, V\) 中各有偶数个叶子,否则 \((u, v)\) 一定会被覆盖。
调整方式即拆开一对连在 \(U\) 或 \(V\) 内的匹配,令其跨 \((u, v)\) 相连,这样显然不劣,故一定有解,答案为缩点后叶子个数除以二上取整。
非割点的贡献是简单的,考虑割点答案构成。
去掉割点后在子树内会形成若干连通块,对于一个大小为 \(x\) 的连通块贡献为 \(x (n - x)\),特别的还要考虑不在割点子树的那个连通块,实现是简单的。
「Note」图论方向 - 图论基础的更多相关文章
- 「NOTE」常系数齐次线性递推
要不是考到了,我还没发现这玩意我不是很会-- # 前置 多项式取模: 矩阵快速幂. # 常系数齐次线性递推 描述的是这么一个问题,给定数列 \(c_1,c_2,\dots,c_k\) 以及数列 \(f ...
- Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门
进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...
- 「kuangbin带你飞」专题十二 基础DP
layout: post title: 「kuangbin带你飞」专题十二 基础DP author: "luowentaoaa" catalog: true tags: mathj ...
- LibreOJ 2003. 「SDOI2017」新生舞会 基础01分数规划 最大权匹配
#2003. 「SDOI2017」新生舞会 内存限制:256 MiB时间限制:1500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 ...
- 「MoreThanJava」Day 4:面向对象基础
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- LOJ #2135. 「ZJOI2015」幻想乡战略游戏(点分树)
题意 给你一颗 \(n\) 个点的树,每个点的度数不超过 \(20\) ,有 \(q\) 次修改点权的操作. 需要动态维护带权重心,也就是找到一个点 \(v\) 使得 \(\displaystyle ...
- LoibreOJ 2042. 「CQOI2016」不同的最小割 最小割树 Gomory-Hu tree
2042. 「CQOI2016」不同的最小割 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 ...
- 「Sqlserver」数据分析师有理由爱Sqlserver之九-无利益关系推荐Sqlserver书单
在前面系列文章的讲述下,部分读者有兴趣进入Sqlserver的世界的话,笔者不太可能在自媒体的载体上给予全方位的带领,最合适的方式是通过系统的书籍来学习,此篇给大家梳理下笔者曾经看过的自觉不错值得推荐 ...
- 「Azure」数据分析师有理由爱Azure之一-Azure能带给我们什么?
前面我们以相同的方式从数据分析师的视角介绍了Sqlserver,本系列亦同样地延续下去,同样是挖掘数据分析师值得使用的Azure云平台的功能.因云平台功能太多,笔者所接触的面也十分有限,有更专业的读者 ...
- 每个程序员都可以「懂」一点 Linux
提到 Linux,作为程序员来说一定都不陌生.但如果说到「懂」Linux,可能就没有那么多人有把握了.到底用 Linux 离懂 Linux 有多远?如果决定学习 Linux,应该怎么开始?要学到什么程 ...
随机推荐
- SpringBoot集成LDAP认证登录
Maven依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="htt ...
- 面试题-Athas性能监控工具(原理部分未完成)
前言 Arthas工具已经被我们项目组简单的应用到了物流项目的日常运维中.物流项目之前出现过生产消费速度不一致导致内存队列中的消息数据积压的问题,在后来解决了问题之后,我们项目组就更加重视了对JVM的 ...
- ZeroTier简单使用
在 CentOS 系统下,你可以使用以下命令行操作来管理 ZeroTier 网络和设备.首先,确保已经正确安装 ZeroTier 软件,你可以按照以下步骤进行安装: 安装 ZeroTier: Zero ...
- P5490 【模板】扫描线 & 矩形面积并 做题笔记
扫描线是一种很常用的 trick,用来计算矩形并周长.并面积.核心思路是使用标记永久化 + 线段树,直接引用朴素的做法,即从某一维度开始扫描并将经过的面积加和. 错误 upd 函数中的汇总不正确,要想 ...
- SpringMvc怎么样把数据带给页面
例子. /** * SpringMVC除过在方法上传入原生的request和session外还能怎么样把数据带给页面 * * 1).可以在方法处传入Map.或者Model或者ModelMap. * 给 ...
- 使用CAMEL创建第一个Agent Society
CAMEL介绍 CAMEL 是一个开源社区,致力于探索代理的扩展规律.相信,在大规模研究这些代理可以提供对其行为.能力和潜在风险的宝贵见解.为了促进这一领域的研究,实现了并支持各种类型的代理.任务.提 ...
- 1678. 设计 Goal 解析器
1678. 设计 Goal 解析器 class Solution { public String interpret(String command) { char[] ch = command.toC ...
- 再见,SSE!你好,Streamable HTTP!轻松开发 Streamable HTTP MCP Server
大家好!我是韩老师. 之前和大家分享了三篇 MCP 相关的文章: Code Runner MCP Server,来了! 从零开始开发一个 MCP Server! 一键安装 MCP Server! 还是 ...
- Linux操作系统(中)
上一篇分享了一些Linux操作系统最基本的一些命令和基础知识,下面,要分享的还是Linux操作系统的一些内容,因为在做网安这方面,Linux会经常用到而且也很重要,好了,废话不多说,要开始了. 在Li ...
- Nacos源码—1.Nacos服务注册发现分析一
大纲 1.客户端如何发起服务注册 + 发送服务心跳 2.服务端如何处理客户端的服务注册请求 3.注册服务-如何实现高并发支撑上百万服务注册 4.内存注册表-如何处理注册表的高并发读写冲突 1.客户端如 ...