AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

提高课第三单元学习笔记(主页有之前的学习笔记,未完)

作者: 作者的头像   c-y-m ,  2025-05-08 21:12:58 · 黑龙江 ,  所有人可见 ,  阅读 4


0


1、简易单源最短路

https://www.acwing.com/activity/content/code/content/9526669/

https://www.acwing.com/activity/content/code/content/9526708/

https://www.acwing.com/activity/content/code/content/9527892/

dijkstra跑最长路

这里其实只需要改变是否需要更新dist[j]的条件和改变堆的类型即可

这里的证明和dijkstra跑最短路的证明一样

https://www.acwing.com/activity/content/code/content/9527985/

https://www.acwing.com/activity/content/code/content/9528098/

https://www.acwing.com/activity/content/code/content/9528297/

2、单源最短路综合

https://www.acwing.com/activity/content/code/content/9528471/

https://www.acwing.com/activity/content/code/content/9529576/

spfa的优化

(1)SLF优化

SLF优化

这里的方法是把较小元素提前

因为先用较小元素更新后面的值, 所以后面的值被更新的次数会变少

这样就可以降低spfa的常数

这里把较小元素提前当然不能用堆优化, 因为这样会又有一个log

我们可以用双端队列, 虽然不能保证单调, 但是有一定的优化效果

如果新入队的元素比队首元素还要小,就加入到队首,否则排到队尾。

(2)topsort + dijkstra解决有负权的 DAG

这个是

DP与最短路

最短路的更新最短路径(dist[j])时相当于DP的状态转移

但是DP只能在DAG上做, 所以可以用最短路来实现

另外dijkstra是无法解决没有累加性质的问题的, 这一点在题目中详细的说了

https://www.acwing.com/activity/content/code/content/9530487/

单源最短路的扩展

求固定终点的最短路(第二种方法更灵活)

2种做法, 第1种是建反图跑dijkstra, 这种做法通俗易懂

第二种做法是新建1个点, 这个点向所有起点连一条长度为0的边

因为这样保证了第1步只在所有的起点上, 并且由于边权是0所以不影响答案

https://www.acwing.com/activity/content/code/content/9530642/

bfs和dijkstra系列满足拓扑序, spfa系列不满足(为什么会在下面中)

https://www.acwing.com/activity/content/code/content/9530987/

https://www.acwing.com/activity/content/code/content/9531181/

https://www.acwing.com/activity/content/code/content/9531291/

4、Floyd

https://www.acwing.com/activity/content/code/content/9531725/

传递闭包:已知一个有向图中任意两点之间是否有连边,要求判断任意两点是否连通(oi-wiki)

这种问题可以用floyd解决, 思想任然是枚举中转点k, 具体见代码

for (int k = 0; k < n; k ++ )
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            d[i][j] |= d[i][k] && d[k][j];

https://www.acwing.com/activity/content/code/content/9531843/

求最小环问题

我们以环上编号最大的一个点划分状态

联想截图_20250511200938.png

任何一个合法的都可以表示成这样

而i->k, j->k只能是固定的w[i, k]和w[k, j]否则无法构成一个环

剩下的i->j就是dist[i, j]

那这样的最短距离就是w[i, k] + w[k, j] + dist[i, j]

最后对于所有的方案取Min, 就是答案

https://www.acwing.com/activity/content/code/content/9532734/

https://www.acwing.com/activity/content/code/content/9532949/
(严格来说不是floyd)

5、最小生成树典型

https://www.acwing.com/activity/content/code/content/9534219/

https://www.acwing.com/activity/content/code/content/9534251/

kruskal的性质:最后一次加进去的边就是最长的一条边

https://www.acwing.com/activity/content/code/content/9534414/

kruskal不做完也是正确的(比如已经有了一些固定边再做kruskal也是对的)

https://www.acwing.com/activity/content/code/content/9534491/

https://www.acwing.com/activity/content/code/content/9534571/

6、最小生成树扩展

https://www.acwing.com/activity/content/code/content/9536143/

https://www.acwing.com/activity/content/code/content/9536203/

经典应用:扩充完全图(题目见下面链接)

完全图的定义:如果任意两个顶点之间都有边的话,该图就称为完全图。

我们先把每个点看做大小是1的联通块, 这个联通快就可以看成完全图

做kruskal算法,当枚举到两个联通块a, b, 我们需要把他们联通块中的每个顶点相连

一共有size[a] * size[b] - 1条边, -1是因为原来a, b之间就有1条边

而边权至少为w[i] + 1, 要不然不能保证新图的最小生成树还是原来那颗

怎么想到kruskal:因为我们要对联通块做最小生成树

https://www.acwing.com/activity/content/code/content/9536441/

次小生成树

做法1:先求最小生成树,然后枚举最小生成树里的边删掉,再求一遍最小生成树

因为最小生成树和次小生成树至少有1条边不一样,然后显然这种做法求得是不严格次小生成树

做法2:先求最小生成树,然后依次枚举非树边,加到最小生成树,同时从原最小生成树中删掉一条边,使得原图任然是一棵树

即求min{sum + w - dist[a][b]}, 其中w > dist[a][b];

就是证明一定存在一个次小生成树和最小生成树只差了一条边

如果差了不止一条边我们其实用最小生成树里的一条边替代更优,所以是对的

并且这种方法能求出严格次小生成树,并且效率更高

https://www.acwing.com/activity/content/code/content/9537089/

7、负环

怎么求负环基础课已经说过了,但是速度是O(nm), 比较慢,这里说一个玄学优化

就是当spfa的效率较慢时,根据经验来说就是存在负环,具体就是所有点的入队次数到达2n左右(或是经验值)就认为有负环(最后一题的优化)

https://www.acwing.com/activity/content/code/content/9537296/

01规划问题:在图论中求一堆的和 / 一堆东西的和最大就是01规划问题

做题套路

1、先二分

2、整理不等式

3、重新定义点权和边权

4、然后就是做一遍比如找负环、dijkstra、kruskal这样的问题

https://www.acwing.com/activity/content/code/content/9537399/

其实spfa找负环的玄学优化不止上面一个

我们可以把队列换成栈,显然栈先进后出的特性能更快找出环,并且这样做绝对是对的

https://www.acwing.com/activity/content/code/content/9537559/

8、差分约束

解决形如xi <= xj + ck的不等式组中所有的x的可行解(ck已知)

我们发现他和最短路的三角不等式很像:如果有(i, j, w)dist[j] < dist[i] + w

那我们发现如果他说xi <= xj + ck, 我们就可以连从j到i的边然后做最短路

最终的dist数组就是答案,无解就是出现负环

注意源点:从源点开始走必须要走到每一条边
(其实对偶一下就是求最长路)

求最大值或最小值

结论:求最小值用最长路,求最大值用最短路

第一先想一下怎么解决x[i] <= c这样的不等式

答案是建超级源点0向i连长度为c的边即可(相当于x[i] <= x[0] + c, 而x[0] = 0)

好,现在我们回到怎么证明这个结论

以求x[i]的最大值为例子

x[i] <= x[j] + c[j]
<= x[k] + c[k] + c[j]
<= x[0] + c[1] + c[2] + ... + c[j]

而x[i]的最大值是所有上界的最小、

而最小的x[0] + c[1] + c[2] + … + c[j]就是求0 -> i的最短路

最小值也可以用这的思考方式得到用最长路

https://www.acwing.com/activity/content/code/content/9537985/

https://www.acwing.com/activity/content/code/content/9539139/

https://www.acwing.com/activity/content/code/content/9539234/

https://www.acwing.com/activity/content/code/content/9539492/

9、最近公共祖先(LCA)

联想截图_20250516192432.png

fa数组用递推求  f[i, j] = f[f[i, j - 1], j - 1]

跳法:我们从大到小枚举k,如果跳2^k步没有达到要求,就利用fa数组跳2^k步,是二进制拆分的思想,也叫倍增

为什么跳到儿子:如果不跳到儿子就无法确定跳到的点是不是最近的公共祖先

log要下取整:比如我们需要4 * 10^4, log一下下去整15即可,因为15能跳2^16 - 1

k从大到小枚举:如果不这样做,假设x和y跳2^0跳到同一深度,然后再跳2^1点就相同了,但是此时x或y并不是lca的儿子

https://www.acwing.com/activity/content/code/content/9539852/

tarjan离线求LCA

离线做法:每次询问直接输出答案,不需要再求
在线做法:每次询问都求1遍

把所有点分成3类

0表示没有访问
1表示正在访问
2表示已经访问并回溯

图片

我们发现当我们处理到x时是可以回答2号区域和x相关的询问

因为二号区域每个节点所在子树的根节点就是x和这个子树中的一个点的lca

那我们就可以把每个子树有并查集处理一下,代表元素就是lca

https://www.acwing.com/activity/content/code/content/9540850/

lca求次小生成树O(n log n)

lca求次小生成树和前面的题思想是一样的,只是用lca优化dfs部分

我们除了维护lca的fa数组,再维护d1[i, j]和d2[i, j], 表示从i开始跳2^j步的最大值和次大值

那这个东西我们可以在bfs的过程中去找方法类似于fa分段去找

int anc = f[i][j - 1];
int distance[4] = {d1[i][j - 1], d2[i][j - 1], d1[anc][j - 1], d2[anc][j - 1]};

然后遍历distance数组去找最大值和次大值

然后再lca跳的过程中,用数组把每一段的d1, d2存起来

最后再比较一下得到a, b路径的次大值和最大值,这样就完成了之前dfs的任务

https://www.acwing.com/activity/content/code/content/9541586/

树上差分

给x到y的每个节点上加c的快速做法

d[x] += c, d[y] += c, d[lca] -= c * 2;

然后每条边的长度对应他下面的节点为根的子树和,很明显这样是对的

10、有向图的强联通分量

对于一个有向图,联通分量:对于分量中的任意2点u, v必然可以从u走到v,且从v走到u

强联通分量:极大的联通分量

有向图 -> 缩点(把所有强联通分量分别看成一个点) -> DAG

tarjan求强联通分量

联想截图_20250517185328.png

有点难说,看模板理解

配合缩点(变成了DAG)

for (int i = 1; i <= n; i ++ )
    for (j, j是i的临点)
        if (id[i] != id[j])     
            add(id[i], id[j]);

并且这里其实按照scc编号递减的顺序排序得到的就是topsort

因为还有一种dfs求topsort的方式

void dfs(int u)
{
    for (j, 有一条u -> j的边)
    {
        dfs(j);
        res[cnt ++ ] = u;   
    }
}

这样明显res是topsort的逆序,而我们发现tarjan的算法过程和他相似

https://www.acwing.com/activity/content/code/content/9542065/

涉及到重要结论
https://www.acwing.com/activity/content/code/content/9542212/

https://www.acwing.com/activity/content/code/content/9542517/

https://www.acwing.com/activity/content/code/content/9542549/

11、无向图的双连通分量

桥:对于一个连通图,如果删掉其中一条边能使图不联通,那么这条边就是桥

边双连通分量e-dcc:极大的不包含桥的联通块

割点:对于一个连通图,如果删掉其中一个点能使图不联通,那么这个点就是割点

点双联通分量v-dcc, 极大的不包含割点的联通块

如何找桥:如果存在dfn(fa) < low(x), 那么x到fa就是一个桥,因为x不能通过回边到fa的fa后再到fa

如何找边双:删掉所有的桥,剩下的每一个都是一个边双

第二种简便的是用stack, 搜到一个就放到栈中,如果dfn[x] == low[x], 说明从x出发走不到上面

说明x到他的父节点就是一个桥,那x所在的子树就是一个边双

技巧:编号为i的边,反边是(i ^ 1)

另外无向图求tarjan只需要判次边是否是反边,不需要in_stk数组

因为in_stk数组是为了看这个点能不能和另一个点组成强联通分量(就是被返祖边和横叉边影响)

而我们仔细想一下:无向图没有横叉边,返祖边无影响(读者自行思考)

模板

涉及重要理论
https://www.acwing.com/activity/content/code/content/9542712/

如何求割点:对于x -> y, 如果low[y] >= dfn[x], 那么如果x不是根节点就是割点

如果x是根节点,那他至少有2个子节点要满足这样的条件

注意下面的题目不是标准求割点,标准的看模板

https://www.acwing.com/activity/content/code/content/9543502/

如何求点双联通分量(伪代码)

孤立点也是点双
if (dfn[x] <= low[y])
{
    cnt ++ ;
    if (x != root || cnt > 1) x是割点;
    将栈中元素直到弹出y为止;
    且显然x也属于次点双
}

正式模板

怎么缩点

1、每个割点单独作为1个点
2、每个点双向他所在点双中整张图的割点连边

https://www.acwing.com/activity/content/code/content/9544788/

12、二分图

(1)二分图 = 不存在奇数环 = 染色法没有矛盾,这里显然,可以用反证法证明充分性和必要性

0 评论

App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息