Me:: Wonderstone

Dirty Deeds Done Dirt Cheap

断断续续学图论也有一段时间了,感觉喜忧参半吧。趁这次集训做总结的机会,把脑子里零零散散的东西理一理,也算是对自己这一阶段的状态做一个交代。

现阶段图论学习进度总结

详见 提高组图论复习清单.pdf,该文档中有对目前的图论知识做详细总结。下面分类给出概述。

1. 基础搜索与遍历

  • DFS:找连通块、判环、记录DFS序、Tarjan类算法、树形DP
  • BFS:无权图最短路、多源BFS、网格图处理
  • 0-1 BFS:边权只有 $0$ 和 $1$ 时的线性优化(双端队列)

2. 最短路算法

  • Dijkstra:边权非负的最短路(优先队列)
  • Bellman-Ford / SPFA:处理负权边、判负环
  • Floyd:小规模全源最短路、传递闭包
  • 分层图最短路:处理”最多使用 $K$ 次特殊操作”问题,即建 $K$ 层图

3. 连通性与缩点

  • 并查集:动态加边、离线删边、辅助最小生成树
  • 强连通分量(Tarjan/Kosaraju):有向图互相可达、缩点后变DAG
  • 割点与桥:无向图的连接
  • 功能图:每个点出度为 $1$ 的特殊结构(环+树)

4. 关于DAG

  • Kahn算法:拓扑排序、判环
  • DAG上DP:方案数、最长路、最短路、博弈状态

5. 最小生成树

  • Kruskal:按边权排序+并查集,解决”连通所有点的最小代价”问题
  • 最小瓶颈性质:最大边权最小的生成树

6. 树上技巧

  • DFS序:子树转区间问题
  • LCA(倍增):树上路径、祖先查询
  • 树的直径:两次DFS/BFS求最长路径
  • 换根DP:每个点作为根时的答案快速计算

7. 二分图

  • 染色判定:是否存在奇环
  • 匈牙利算法:最大匹配、增广路思想
  • Kőnig定理:最小点覆盖=最大匹配(仅二分图成立)

8. 高级技巧

  • 状态图建模:点=位置/资源/状态
  • 反图技巧:从终点反向BFS/最短路
  • 补图BFS:用set维护未访问点,避免显式建补图
  • 欧拉路径:边恰好经过一次的判定
  • 二分答案+图判定:单调性验证

题目练习情况总结

以下为近一年(两学期)的图论练习清单:

主题/平台 NKOJ VJudge
图论基础 \ Graph_First_Lesson
树形结构基础 BTreeEx
差分约束 差分约束系统 \
树形DP 树形dp入门 树形DP补充
SCC 图的连通性 夏季专题训练三
二分图 二分图练习 夏季专题训练三
综合练习&补充 \ 夏季专题训练三

通过对表中所有练习的完成情况进行复盘,我发现几个主要问题:

  1. 做题速度太慢。几十道题目中我很少拿到首A,大概就是开始看题时别人已经快想出来了、开始写代码时别人早就过了。这导致我思考时就多半已经有人过了这道题,如果这道题又碰巧特别难,自己就总是想向同学求助——这般被动的学习无疑是对自己的思维成长无益的。
  2. 容错率太低。两个OJ都可以看到总耗时/罚时,数据清楚地暴露了我这方面的缺陷。其中VJudge还可以显示被罚时的次数,我那栏时有一个-5-8甚至-10,也就是说同一道题我有时需要提交将近十次才能通过,CF/ICPC赛制都算仁慈了,乐多对我来说肯定是最不友好的。这说明我做题时的审题以及(也尤其是)实现时调试的能力都有些欠佳。

但同时,在对比近期作业表现(如 夏季专题训练三)和早期练习表现(如 BTreeEx)后,我也发现了一些进步:

  1. 整体的平均完成度相较于之前有提高。九次作业有七次作业最后完成了所有题目,多做一道题就会收获更多经验。

总体来看,当前的主要问题不在于忘了某个算法的代码,而在于读题后无法快速锁定正确的模型,导致前期思考效率偏低。至于罚时偏高这件事,本质上是调试习惯不好。自我统计一下就会发现,对于一道提高+以上的题目,我平均花在调题上的时间会是写题时间的两倍甚至以上。横向对比一下班上的同学,朋友冯克一调题时就经常以一种我看不懂的诡异方式在代码上到处乱改,两分钟就能把原代码改得面目全非——但是从效果上看,他调题的效率就高得惊人,我实在是惭愧不堪啊。速度方面,短期内可能不会有质的提升,但可以尝试定时训练的方式。读题后五分钟内必须给出一个初步的算法方向,哪怕不确定也比空耗时间要好。

做题技巧总结

上文的PDF原文中已经写得非常详细了,我在这里只补充几条自己觉得确实有用的心得。

  1. 拿到题先问三个问题。 点是什么?边是什么?边有没有方向和权值?如果点没法直接对应到题目里的对象,说明大概率需要建状态图;如果边权有0有1,优先想0-1BFS而不是Dijkstra;如果题目里出现了“互相到达”“互相影响”这类描述,往强连通分量上靠。

  2. 反图是个好东西。 当题目问“所有点中哪些能到达目标点”时,直接对每个点做搜索是笨办法。建反图,从目标点出发跑一次,效率高得多。

  3. 分层图的本质是扩展状态。 不要把它想得太高级,其实就是把当前用了掉的机会数塞进点的编号里,剩下的就是普通最短路。关键点在于估计好总点数和总边数,不要开小了。

  4. 树上路径用LCA,子树问题用DFS序。 一旦判断出原题是树上的问题,优先想这两个工具能不能套上。如果题目要求每个点作为根时的答案,那就是换根DP,第一遍算子树贡献,第二遍做换根转移,套路比较固定。

  5. 关于虚拟源点。 多个起点时建一个虚拟源点连边,代码更干净。虚拟汇点同理,这种小技巧能降低思维的负担。

  6. 关于反向建模。 反图除了用在最短路和BFS上,还有一个用处是处理删除操作。

  7. 关于缩点后的DAG处理。 首先,新图的边需要去重,否则拓扑排序时入度会被重复计算,导致DP顺序错乱。去重的常见做法是用set或者排序后unique,图省事就用 unordered_map<pair<int,int>>,虽然多一个log但数据规模通常能接受。其次,缩点后每个分量的权值合并要慎重,如果原题是点权和,那就把分量内所有点权加起来;如果是问是否存在特殊点,那就用或运算;如果是问路径条数,环内可能无穷多条,那缩点后就不能直接转移了,需要另想办法。

  8. 关于二分图匹配的调试。 匈牙利算法本身很短,但出错的概率一点也不低。最常见的问题是 vis 数组的标记方式。如果用时间戳(即把 vis 改成int,每次dfs传入一个tag)可以免去每次清空O(n)的开销,但要注意tag不能用 $0$,因为 match初始为 $0$。

  9. SPFA毒瘤吗。 SPFA的确最好想也好写,但是它被卡的情况太常见了。我的感受是除非有负权边且数据范围很小,否则尽量别用SPFA。如果负权边数量不多,可以考虑把图转成DAG后用拓扑DP,或者用Bellman-Ford。差分约束题负环的存在本身就是需要判定的,这时候只能用SPFA,但要清楚它的复杂度在最坏情况下是O(nm)。

总结

接下来的集训里,重点会放在两件事上:一是提高建模的敏感度,尽量做到读完题就能判断出该往哪个方向想;二是降低提交次数,把该踩的坑在本地踩完,而不是在OJ上反复试错,因为正规比赛采用的OI赛制没有任何试错机会。至于速度,能提就提吧,提不了至少保证想清楚之后再动键盘,避免做到一半发现思路错了就全部推倒重来。

题目 Mini-Machine & The Loop

题目背景

1936年,艾伦·图灵提出了图灵机的理论模型。一台图灵机由一条无限长的纸带、一个能够读写符号并改变状态的控制器组成。尽管结构如此简单,它却能够执行任何机械可计算的过程,为现代计算机科学奠定了基础。今天的计算机虽然复杂得多,其核心依然遵循着冯·诺依曼体系结构:存储程序,顺序执行

题目描述

小明设计了一台迷你虚拟机,该机器包含 4 个 64 位有符号整数寄存器,名称分别为 r0, r1, r2, r3
虚拟机能够执行一个由若干指令组成的程序。初始时所有寄存器的值均为 0,程序从第 1 行开始顺序执行,直到遇到 HLT 指令时停机。

虚拟机支持的指令集如下(每行一条指令,操作数之间用单个空格分隔):

指令格式 含义
MOV dst, src 将源操作数 src 的值复制到目标寄存器 dstdst 必须是寄存器名;src 可以是寄存器名或整数常量(如 123-5)。
ADD dst, src dst = dst + srcsrc 可以是寄存器名或整数常量。
SUB dst, src dst = dst - srcsrc 可以是寄存器名或整数常量。
JMP offset 无条件跳转到“当前行号 + offset”的位置。offset 是一个整数。行号从 1 开始计数。
JNZ reg, offset 若寄存器 reg 的当前值 不为 0,则跳转到“当前行号 + offset”;否则顺序执行下一条。
HLT 停机,虚拟机立即退出。

程序结构保证

  • 程序有且仅有一条跳转指令,是一条 JNZ,且它的 offset 为负数(即往回跳),从而形成一个唯一的循环
  • 这个 JNZ 所在的循环体内部没有任何其它跳转指令。也就是说,循环体由若干条纯算术指令(MOVADDSUB)组成,最后紧跟着一条 JNZ
  • 循环之前可能有一段顺序执行的“前缀”指令(也可以为空);在 JNZ 之后只有一条 HLT 指令,后面没有任何指令。
  • 程序保证会最终停机(循环一定会终止)。
  • 所有寄存器值、常数以及中间结果均在 $[-10^{18},10^{18}]$ 范围内,最终答案也保证在此范围内。

现在,给你一段符合上述约束的程序,请你输出虚拟机停机时 r0, r1, r2, r3 的值。

输入格式

第一行一个整数 M,表示指令的总行数。
接下来 M 行,每行一条指令,格式如上所述。行首行尾没有多余空格,操作数之间用单个空格隔开。

输出格式

一行四个整数,依次为寄存器 r0, r1, r2, r3 的值,之间用空格分隔。

样例输入 1

1
2
3
4
5
6
7
6
MOV r0, 5
MOV r1, 0
ADD r1, r0
SUB r0, 1
JNZ r0, -2
HLT

样例输出 1

1
0 15 0 0

解释
前缀指令将 r0 置为 5,r1 置为 0。
循环体为第 3~5 行:

  • ADD r1, r0r1 += r0
  • SUB r0, 1r0 -= 1
  • JNZ r0, -2 → 若 r0 != 0 则跳回第 3 行

该循环执行 5 次:r1 依次累加 5, 4, 3, 2, 1,总和为 15;r0 最终变为 0,退出循环,执行 HLT

样例输入 2

1
2
3
4
5
6
7
5
MOV r0, 1000000000
MOV r1, 0
ADD r1, 3
SUB r0, 1
JNZ r0, -2
HLT

样例输出 2

1
0 3000000000 0 0

数据范围与提示

  • 对于 30% 的测试数据,循环实际执行次数 ≤ 1000。
  • 对于 100% 的测试数据,1 ≤ M ≤ 2000,循环执行次数可能高达 $10^{12}$。所有数值在 64 位有符号整数范围内。

Me

我是人,男,2012年9月30日生,还没死。

现在就读于重庆市南x中学初一x班,正在学习 OI。

呃,没什么好说的,平时就喜欢听音乐、喝奶茶和找某同学白嫖他 Steam 上的游戏,不喜欢吃蘑菇

这个 GitHub Page 的个人站于公元2026年4月5日,同年清明节前一天创建。

User

放一下在其他网站上的账号,有一些不方便透露就不放了。

  • 洛谷:WonderStone_

  • 酷狗:Wonderstone

  • QQ:WonderStone.

  • GitHub:Wonderstone-0930

  • ……

没了。

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

0%