for (inti=0;i < n;i++){ if (!vis[i]) DFS(i) //或BFS(i) }
DFS
1 2 3 4 5 6 7 8
voiddfs(int u) { vis[u] = 1; for (inti= head[u]; i; i = e[i].x) { if (!vis[e[i].t]) { dfs(v); } } }
BFS
1 2 3 4 5 6 7 8 9 10 11 12 13
voidbfs(int u) { Q.push(u); vis[u] = 1; while (!Q.empty()) { u = Q.front();Q.pop(); for (inti= head[u]; i; i = e[i].next) { if (!vis[e[i].to]) { Q.push(e[i].to); vis[e[i].to] = 1; } } } }
拓扑排序
前提:有向无环图(DAG)
核心:维护入度为 0 的顶点集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
List<Integer>[] g = newList[N]; int[] indeg = newint[N]; //入度 List<Integerres; //拓扑排序结果 for (inti=0;i < m;i++){ int u, v; indeg[v]++; g[u].add(v); } Deque<Integerq = newLinkedList<>(); for (inti=1;i <= n;i++) if (indeg[i] == 0) q.offerLast(i); while (!q.isEmpty()){ intu= q.pollFirst(); res.add(u); for (int v : g[u]){ if (--indeg[v] == 0) q.offerLast(v); } } if (res.size() == n) // DAG 可以拓扑排序 else// 无法拓扑排序
int[] dis = newint[n*m]; Arrays.fill(dis, 1 << 30); Deque<int[]pq = newLinkedList<>(); pq.add(newint[]{0, 0}); dis[0] = 0; while (!pq.isEmpty()){ int[] cur = pq.pollFirst(); intu= cur[0]; for (int v : g[u]){ intp= grid[x][y], nd = dis[u]+p; if (nd < dis[v]){ dis[v] = nd; if (p == 0) pq.offerFirst(newint[]{nd, v}); //边为0 加入队首 else pq.offerLast(newint[]{nd, v}); //边为1 加入队尾 } } }
不同方法的比较
Bellman-Ford
最短路算法
Floyd
Dijkstra
Johnson
单源最短路
最短路类型
每对结点间的最短路
单源最短路
每对结点间的最短路
任意图
作用于
任意图
非负权图
任意图
能
能否检测负环?
能
不能
能
中/小
作用图的大小
小
大/中
大/中
O(NM)
时间复杂度
O(N3)
O((M + N)log M)
O(NMlog M)
注:表中的 Dijkstra 算法在计算复杂度时均用
priority_queue 实现。
分层图最短路
分层图最短路,如:有 k
次零代价通过一条路径,求总的最小花费。对于这种题目,我们可以采 用 DP
相关的思想,设 disi, j
表示当前从起点 i
号结点,使用了 j
次免费通行权限后的最短路径。 显然,dis 数组可以这么转移:
disi, j = min {min {disfrom, j − 1}, min {disfrom, j + w}}
其中, from 表示 i
的父亲节点, w
表示当前所走的边的边权。当 j − 1 ≥ k 时, disfrom, j = ∞
。 事实上,这个 DP 就相当于把每个结点拆分成了 k + 1
个结点,每个新结点代表使用不同多次免费
通行后到达的原图结点。换句话说,就是每个结点 ui 表示使用
i 次免费通行权限后到达 u 结点。
staticint test, N = 10010, M = (int)1e9+7; staticint n, m, k; static List<int[]>[] g = newList[N]; staticint[][] dis = newint[N][11], vis = newint[N][11]; staticvoidsolve()throws Exception { n = ni(); m = ni(); k = ni(); ints= ni(), t = ni(); for (inti=0;i < n;i++){ g[i] = newArrayList<>(); Arrays.fill(dis[i], M); } for (inti=1;i <= m;i++){ intu= ni(), v = ni(), w = ni(); g[u].add(newint[]{v, w}); g[v].add(newint[]{u, w}); } PriorityQueue<int[]pq = newPriorityQueue<>((x, y)-> x[2]==y[2] ? x[1]-y[1] : x[2]-y[2] ); pq.add(newint[]{s, 0, 0}); dis[s][0] = 0; while (!pq.isEmpty()){ int[] cur = pq.poll(); intu= cur[0], c = cur[1], d = cur[2]; if (vis[u][c] == 1) continue; vis[u][c] = 1; for (int[] ne : g[u]){ intv= ne[0], w = ne[1]; if (dis[v][c] d+w){ dis[v][c] = d+w; pq.add(newint[]{v, c, dis[v][c]}); } if (c+1 <= k && dis[v][c+1] d){ dis[v][c+1] = d; pq.add(newint[]{v, c+1, dis[v][c+1]}); } } } intans= M; for (inti=0;i <= k;i++) ans = min(ans, dis[t][i]); pw.println(ans); }
差分约束(不等式)
差分约束系统 是一种特殊的 n
元一次不等式组,它包含 n
个变量 x1, x2, …, xn
以及 m
个约束条件,每个约束条件是由两个其中的变量做差构成的,形如 xi − xj ≤ ck
,其中1 ≤ i, j ≤ n, i ≠ j, 1 ≤ k ≤ m
并且 ck
是常数(可以是非负数,也可以是负数)。
差分约束系统中的每个约束条件 xi − xj ≤ ck
都可以变形成 xi ≤ xj + ck
,这与单源最短路中 的三角形不等式 dist [y] ≤ dist [x] + z
非常相似。因此,我们可以把每个变量 xi 看做图中的一
个结点,对于每个约束条件 xi − xj ≤ ck
,从结点 j 向结点 i 连一条长度为 ck
的有向边。
注意到,如果 {a1, a2, …, an}
是该差分约束系统的一组解,那么对于任意的常数 d , {a1 + d, a2 + d, …, an + d}
显然也是该差分约束系统的一组解,因为这样做差后 d 刚好被消掉。
staticvoiddfs(int u, int f) { //u当前节点 f为父节点 搜索从根节点开始即dfs(root, 0) deep[u] = deep[f]+1; fa[u][0] = f; //u的2^0级祖先 即 u的1级祖先是其父节点 for (inti=1;(1 << i) <= deep[u];i++){ fa[u][i] = fa[fa[u][i-1]][i-1]; } for (int v : g[u]) if (v != f) dfs(v, u); //父节点不再访问 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
staticintLCA(int u, int v) { if (deep[u] deep[v]) {inttp= v;v = u;u = tp;} //让v的深度始终是大于u的 intdis= deep[v]-deep[u]; //深度差 //将u的深度变得与v相同 for (inti=0;(1<<i) <= dis;i++){ //快速加的意思 任意正整数都可以表示为几个2的幂次的和 if (((dis>>i) & 1) == 1) v = fa[v][i]; } if (u == v) return u; for (inti=19;i >= 0;i--){ if (fa[u][i] != fa[v][i]){ u = fa[u][i]; v = fa[v][i]; } } return fa[u][0]; }
staticint n, m, ans; static List<Integer>[] g; staticint[] d1, d2; // d1[i]存储节点i到叶子节点的最大距离 d2[i]存储节点i到叶子节点的第二大距离 staticvoidsolve()throws Exception{ dfs(1, 0); out.println(ans); } staticvoiddfs(int u, int f){ d1[u] = d2[u] = 0; for (int v : g[u]){ if (v == f) continue; dfs(v, u); intt= d1[v]+1; if (t d1[u]){ d2[u] = d1[u]; d1[u] = t; }elseif (t d2[u]){ d2[u] = t; } } ans = max(ans, d1[u]+d2[u]); }
Kruskal 重构树
设 fi
为 i 所在联通块的根。
算法流程和 Kruskal 最小生成树的过程非常类似:
将所有边按边权从小到大排序
顺序遍历每条边 (u, v, w) ,若
u, v
已经联通跳过,否则建立一个新点 x ,让 x 作为 fu 与 fv 的父亲(即连
x ⇒ fu
和 时间复杂度 O(mlog m + nlog n)
。 最后,以最后一个建立的新点作为 rt
,就是一颗重构树了(下面是一个无向图联通变成重构树的例子,排序后第 i 条边的编 号是 n + i
,点权是红色,蓝色是新点,黑色是原来的点)。
这棵树有如下性质:
原树若有 n
个节点,那么新树有 2n − 1
个节点,根是 2n − 1
。因为建的新点就是合并两个点的次数,合并 n − 1 次。最
后一次合并作为根,凑成了整个树。
所有原来的点就是叶子节点。因为建新图过程中我们没有让原来的点当父亲。
对于任意的 x 点,
它的祖先链从下往上点权都是非严格递增的。因为每次合并的时候,只有 ≤ w 的边都构造好了,所以此时 fu 的点权也
≤ w。
因此重构树的点权是一个大根堆。跟上一个性质的等价的。
对于一个 x 和一个值
v 。从 x 出发只经过 ≤ v 的边能到达的点集 = x 的祖先节点中深度最小的点权
≤ v 的点 z 的子树中的
原来的点集。(证明:这颗子树外的点显然不行,因为再往上点权 > v ,说明再往上其他的点使通过
> v 的边才和 x 点连上
的,所以不行;这颗子树内的点显然可以,因为这是一个大根堆,所以子树内的点都可以用
≤ v 的边互相可达,他们在新树
上的路径,经过的所有编号就是原树上经过的所有边。
对于任意 x, y
,其最小瓶颈边权 (使其最大边最小的路径的最大边) 为 x, y 在新树上的 LCA 点权。
x, y 在经过 LCA
这条边 后恰好联通,由于从小到大顺序执行,说明这条边是路径上最大的边。
如果求最大生成树,反着排序,那么偏序关系都反转,就不赘述了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
for (inti=0;i < m;i++){ intu= ni(), v = ni(), w = ni(); e[i][0] = u; e[i][1] = v; e[i][2] = w; }Arrays.sort(e, 0, m, (x, y)->y[2]-x[2]); intcur= n; for (inti=0;i < m;i++){ intu= e[i][0], v= e[i][1], w = e[i][2]; intx= find(u), y = find(v); if (x != y){ ++cur; fa[x] = fa[y] = cur; g[cur].add(x); g[cur].add(y); val[cur] = w; } }
2-11 就是重链,2-5
就是轻链,用红点标记的就是该结点所在重链的起点,也就是下文提到的 top
结点,
还有每条边的值其实是进行 dfs 时的执行序号。
变量声明:
1 2 3 4 5 6
staticint n, m, root, time = 0; staticlong[] f = newlong[N<<2], lazy = newlong[N<<2]; staticint[] siz = newint[N], dep = newint[N], son = newint[N]; staticint[] dfn = newint[N], top = newint[N], faz = newint[N]; staticlong[] w = newlong[N], rk = newlong[N]; static List<Integer>[] g = newList[N];
回顾上文的那个题目,修改和查询操作原理是类似的,以查询操作为例,其实就是个
LCA,不过这里使用了 top 来进行加速,因为 top
可以直接跳转到该重链的起始结点,轻链没有起始结点之说,他们的 top
就是自己。需要注意的是,每次循环只能跳一次,并且让结点深的那个来跳到 top
的位置,避免两个一起跳从而插肩而过。
1 2 3 4 5 6 7
while (top[u] != top[v]){ if (dep[top[u]] < dep[top[v]]) {int tp = u;u = v;v = tp;} upd(1, 1, n, dfn[top[u]], dfn[u], val); u = faz[top[u]]; } if (dep[u] dep[v]) {int tp = u;u = v;v = tp;} upd(1, 1, n, dfn[u], dfn[v], val);