Content:图论
Date:2025.8.11
课堂内容
差分约束
Problem
给定一个包含 n 个不等式的不等式组,要求求出这个不等式组的任意一组解,或者判断不等式组无解。
Solution
对于给定的这个问题,整理一下,发现要求解:
⎩⎨⎧x1−x2>c1x2−x3>c2x3−x4>c3…xn−x1>cn⇒⎩⎨⎧x1>x2+c1x2>x3+c2x3>x4+c3…xn>x1+cn
容易发现这些不等式的形式和 Dijkstra 中三角不等式 disu>disv+w 类似。更进一步地,我们发现如果将不等式组中的不等式转化成边 (xi,xi+1,ci) 的话,其实的解就是 disi。如果原问题无解的话,相当于在图上存在负环。至此,问题解决。
需要注意的是,因为要判断是否存在负环,所以不能使用 Dijkstra,而是要使用 SPFA 解决此问题。
LCA(最近公共祖先)
Problem
给点一棵树,每次询问 (u,v) 的最近公共祖先 LCA(u,v)。
Solution
倍增
首先我们处理出所有点的直接父亲是谁,然后利用倍增的思路,处理出这个点向上跳一条长度为 2k 的链后它的父亲是谁。
具体地,我们需要得到一个数组 jump[u][k] 表示节点 u 向上跳 2k 步后他的父亲是谁,即 jump[u][k]=jump[jump[u][k−1]][k−1],先向上跳 2k−1 步,再跳 2k−1 步,就得到了我们想要的答案。
查询的时候只需要暴力向上跳即可,复杂度 O(mlogn)。
树剖
我们先对原树做重链剖分,然后查询的时候每次跳一条重链,直到两条重链的链头的父亲相同。复杂度 O(mlogn)。
ST 表
我们在原树上处理出欧拉序,由于原树上的任意一颗子树在欧拉序上对应了一个连续的区间,所以问题就转化为了查询这个区间内深度最小的节点的 RMQ 问题,可以用 ST 表解决,复杂度为预处理 O(nlogn),查询 O(1)。
Tarjan(离线)
我们将所有询问离线下来,然后在 Tarjan 的时候判断与当前这个点有关的所有查询中,是否存在另一个点已经被访问了的查询,如果存在,就更新即可。期间用并查集维护 LCA,复杂度 O(α(m+n,n)+n)。
例题
ARC084B Small Multiple
Problem
给定一个数字 k,要求找到最小的 k 的倍数,使得所有数位上的数字和最小。
Solution
我们不难发现,一个数 x∣k 等价于如果我们用 ci 表示 x 的每一个数位上的数,则 ∀i,ci×10imodk=0。所以我们可以对于每一个数位考虑。不难发现,每一次操作就是 ×10 或者 +1,所以我们可以 0/1 BFS。
提交记录:Link
Problem
给定共 m 个以下形式的不等式组:
- a−b≥c;
- a−b≤c;
- a=b;
求其中任意一组解。
Solution
我们将题目中的不等式组变换一下:
- a−b≥c→b≤a+c;
- a−b≤c→a≤b+c;
- a=b→a≤b 和 b≤a;
所以我们按照差分约束的方式建图即可。
提交记录:Link
Problem
给你 n 扇门和 m 个开关,每个开关控制 k 扇不同的门,一扇门最多被两个开关控制。初始时每扇门要么打开,要么关闭。变换一个开关的状态会使门的状态也发生变化,问你存不存在一种方案,使得所有门均开启。
Solution
不难发现,最初打开的门所对应的开关要么同时打开,要么同时关闭,所以我们可以用并查集维护每个开关的状态。若果这个门最初是关闭的,则将 (x,y) 和 (x+m,y+m) 合并,否则将 (x+m,y) 和 (y+m,x) 合并。最后检查每个开关的状态是否合法即可。
提交记录:Link
模拟题-5.2 第 K 大查询
Problem
给定一个 1∽n 的排列 a,想知道所有满足 r−l+1≥k 的区间 [l,r] 内第 K 大的数的和是多少。
Solution
我们将原来的排列按照从小到大排序,发现一个数在区间 [l,r] 是第 k 大当且仅当区间中包含 k−1 个比它大的数。所以我们可以用双向链表维护这个思路。每次暴力像两边扩展,然后计算贡献,最后在双向链表中删除这个数。时间复杂度 O(nk)。
Code
#include <algorithm> #include <iostream>
using std::cin; using std::cout;
constexpr int N = 5e5 + 5; int n, k, arr[N], index[N]; long long answer = 0;
struct Node { int left, right; } link[N];
int main() { std::ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); cin >> n >> k; for (int i = 1; i <= n; i++) { cin >> arr[i]; index[i] = i; link[i] = (Node) {i - 1, i + 1}; } std::sort(index + 1, index + n + 1, [&] (int a, int b) { return arr[a] < arr[b]; }); for (int i = 1; i <= n; i++) { int _index = index[i]; int left = _index, right = _index; int rank = 1; while (link[left].left != 0 && rank < k) { rank++; left = link[left].left; } while (link[right].right != n + 1 && rank < k) { rank++; right = link[right].right; } if (rank == k) { while (left != link[_index].right && right != n + 1) { answer += 1ll * (left - link[left].left) * (link[right].right - right) * arr[_index]; left = link[left].right; right = link[right].right; } } link[link[_index].left].right = link[_index].right; link[link[_index].right].left = link[_index].left; } cout << answer << '\n'; return 0; }
|