9.5 图论杂题
「Stoi2029」以父之名
给定一张无向图,边权只有 \(1, 2\),你需要给每个边定向,使得每个点的入边权值之和与出边权值之和的差值的绝对值等于 \(1\)。
\(1 \le n \le 10^6, 1 \le m \le 3 \times 10^6\),\(w_i \in \{1, 2\}\)。
首先边权为 \(1\) 的情况很简单,只需要用一个超级源点连一下奇度点,跑欧拉回路就可以了。由于保证有解,所以所有点连到的 \(1\) 边个数都是奇数。
然而有两种边权,其实也不是很难,把每个点拆成两个放在两边,左边的图只连 \(1\) 边,右边的图只连 \(2\) 边。由于欧拉回路的性质,我们可以做到最后两个图中每个点的出入度差值不超过 \(1\)。分类讨论:
- 当 \(2\) 边的个数为奇数时,右边的图会给该点贡献 \(\pm 2\),由于最终我们要求绝对值恰好为 \(1\),所以左边的图要与右边的图的贡献匹配,应该贡献 \(\mp 1\)。即左边正的匹配右边负的,右边正的匹配左边负的,用一条连接两个图中同源点(由一个点拆分而来)限制这个关系即可。
- 当 \(2\) 边的个数为偶数时,右边的图不做贡献,最终只需要把左边的点连给超级源点即可。
最后直接跑板子就完了,不需要任何对板子的魔改。
[AGC056C] 01 Balanced
构造一个 \(0/1\) 序列,满足 \(m\) 条限制,分别表示序列在 \([L_i, R_i]\) 区间的 \(0/1\) 个数相同。满足字典序最小。
\(2 \le n \le 10^6, 1 \le m \le 2\times 10^5, 1 \le L_i < R_i \le n, R_i-L_i\equiv 1 \pmod 2\)。
将 \(0/1\) 变成 \(1/-1\),构造前缀和序列 \(s\),问题变为构造 \(s\),满足:
- \(\forall 1 \le i \le m, s_{R_i}=s_{L_i-1}\)。
- \(\forall 0 \le i < n,s_i-1 \le s_{i+1} \le s_i+1, s_i \neq s_{i+1}\)。
那么问题变为最大化 \(s\) 的字典序,用差分约束跑最短路即可。把相等的缩点,观察到建出差分约束系统后,这是一个由下标奇偶性决定左右的二分图,边权只有 \(1\),所以直接跑最短路就是字典序最大,而且不存在 \(s_i = s_{i+1}\)。
[PA 2015] Kontrmanifestacja
给定一张 \(n, m\) 的有向图,求图中所有回路点集的交(即删去该点后原图成为 DAG 的点)。
\(2 \le n \le 5 \times 10^5, 1 \le m \le 10^6\)。
时隔 7 个月,终于把这个🐦题给补了 /ll。
不要去管后面那个转化题意,因为 DAG 除了其拓扑结构外没有什么性质。考虑求所有回路的交,我们跑出一个环,那么回路点集的交一定是这个环上的点集的子集。考虑那些点不在交上,只要我们能够通过不走这个环上的路径从环点 \(u\) 出发走到环点 \(v\),那么 \(u\) 到 \(v\) 之间的点都是废掉的(考虑一下方向。)
考虑到如果回路点集的交非空,则把这个环上的点删掉后,原图是一个 DAG(⚠️这里不是删掉环上的边,因为环上的点还可以连一个新环,交仍然不为空),可以在 DAG 上面 dp 出每个点在仅起点和终点可以是环点的情况下能到达的环上编号最大的位置和编号最小的位置(排除自己),分别记为 \(f, g\),此处编号顺序满足 \(C_i \to C_{i+1}\) 是一条环边。dp 是容易的,但是有一个小问题。
由于 \(i \rightsquigarrow f_i( f_i > i, i \in C)\) 的路径是满足与环的前进方向相同的,所以相当于可以通过这个路径不经过 \((i, f_i)\) 中间的所有点走一条回路。但是对于 \(i \rightsquigarrow g_i(i > g_i, i \in C)\) 的路径是满足与环的前进方向相反的,所以这个路径可以不经过 \((i, |C|] \cup [1, g_i)\),那么此时我们又希望 \(g_i\) 尽可能大了,不过这就没办法 dp 了。注意到这里可以拆分为两个部分 \((i, |C|]\) 和 \([1, g_i)\),对于 \(i\),我们实际上是不关心 \(g_i\) 具体值的,而仅仅只需要知道此处 \(i\) 能否到达一个编号小于它的点,所以只要 \(g_i < i\) 就直接覆盖 \((i, |C|]\)。反过来也是一样的,枚举 \(i\) 反着找最大的 \(j\) 能够到达 \(i\),记为 \(h_i\),如果 \(h_i > i\),那么就直接覆盖 \([1, i]\)。
覆盖到的地方就不属于回路点集的交,直接用差分统计就完了。温馨提示:给出了 \(1\) 个 hack。温馨提示 2:有图图可以看
1 2
1 10
2 3
2 9
3 4
4 5
5 1
5 8
5 9
8 1
9 1
9 3
10 2
answer:
0
explain: u 和 v 可以相同。
顺便提供一个 DeepSeek 写的造小数据的程序(大了出现不交环的概率就会比较大):
内容由 AI 生成
#include <iostream>
#include <fstream>
#include <vector>
#include <set>
#include <random>
#include <chrono>
#include <algorithm>
void generate_graph(int n, int k, double p, int d, const std::string& filename = "test.in") {
// 参数修正
if (n < 2) n = 2;
if (k < 2) k = 2;
if (k > n) k = n;
if (d < 1) d = 1;
if (d > k) d = k;
std::mt19937 rng(static_cast<unsigned>(
std::chrono::steady_clock::now().time_since_epoch().count()
));
std::bernoulli_distribution coin(p);
std::uniform_int_distribution<int> cnt_dist(1, d);
std::set<std::pair<int, int>> edges;
// 核心环:1->2->...->k->1
for (int i = 1; i <= k; ++i) {
int j = (i == k) ? 1 : i + 1;
edges.insert({i, j});
}
// 外部顶点随机插入核心环的边
for (int v = k + 1; v <= n; ++v) {
if (!coin(rng)) continue;
int m = cnt_dist(rng);
std::vector<int> candidates(k);
for (int i = 0; i < k; ++i) candidates[i] = i + 1; // 主环边编号(起点)
std::shuffle(candidates.begin(), candidates.end(), rng);
candidates.resize(m);
for (int i : candidates) {
int j = (i == k) ? 1 : i + 1;
edges.insert({i, v});
edges.insert({v, j});
}
}
std::ofstream fout(filename);
if (!fout) {
std::cerr << "无法打开文件 " << filename << std::endl;
return;
}
fout << n << " " << edges.size() << "\n";
for (auto &e : edges) {
fout << e.first << " " << e.second << "\n";
}
fout.close();
}
int main(int argc, char* argv[]) {
int n = 10; // 总顶点数
int k = 5; // 核心顶点数(环路交集大小)
double p = 0.7; // 外部顶点插入概率
int d = 2; // 每个外部顶点最多插入的主环边数
if (argc >= 2) n = std::stoi(argv[1]);
if (argc >= 3) k = std::stoi(argv[2]);
if (argc >= 4) p = std::stod(argv[3]);
if (argc >= 5) d = std::stoi(argv[4]);
generate_graph(n, k, p, d);
return 0;
}
正解代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
#define drep(i, r, l) for (int i = (r); i >= (l); --i)
#define PII pair<int,int>
#define fi first
#define se second
#define eb emplace_back
#define FRE(p) freopen(#p".in","r",stdin),freopen(#p".out","w",stdout)
using namespace std;
const int N = 5e5+5, P = 998244353;
int n, m, he[N], ne[N<<2], ver[N<<2], deg[N], tot;
inline void adde(int u, int v) { ne[++tot] = he[u], ver[tot] = v, he[u] = tot; }
int vis[N], st[N], tp, c[N], len, b[N], a[N], cnt;
bool find(int x) {
st[++tp] = x, vis[x] = 1;
for (int i = he[x], y; i; i = ne[i]) {
if (vis[y = ver[i]] == 1) {
int z;
do c[++len] = z = st[tp--]; while (z ^ y);
return true;
}
if (!vis[y] && find(y)) return true;
}
--tp, vis[x] = 2;
return false;
}
queue<int> q;
int f[N], g[N], d[N];
#define G(x, val) (f[x] = max(f[x], (val)), g[x] = min(g[x], (val)))
#define F(x, y) (f[x] = max(f[x], f[y]), g[x] = min(g[x], g[y]))
signed main() {
FASTIO;
#ifdef LOCAL
FRE(test);
#endif
cin >> n >> m;
rep(i, 1, m) {
int u, v; cin >> u >> v;
adde(u, v);
}
rep(i, 1, n) if (!vis[i] && find(i)) break;
if (!len) return cout << "NIE\n", 0;
reverse(c+1, c+len+1);
rep(i, 1, len) b[c[i]] = i;
rep(x, 1, n) if (!b[x]) for (int i = he[x], y; i; i = ne[i]) if (!b[y = ver[i]]) ++deg[y];
rep(i, 1, n) if (deg[i] == 0) q.push(i);
while (q.size()) {
int x = q.front(); q.pop();
if (b[x]) continue; a[++cnt] = x;
for (int i = he[x], y; i; i = ne[i]) if (!b[y = ver[i]] && !--deg[y]) q.push(y);
}
if (cnt ^ n-len) return cout << "0\n", 0;
rep(x, 1, n) if (!b[x]) {
f[x] = -1, g[x] = n+1;
for (int i = he[x], y; i; i = ne[i]) if (b[y = ver[i]]) G(x, b[y]);
}
drep(id, n-len, 1) for (int i = he[a[id]], y; i; i = ne[i]) if (!b[y = ver[i]]) F(a[id], y);
rep(i, 1, len) {
int x = c[i]; f[x] = -1, g[x] = n+1;
for (int j = he[x], y; j; j = ne[j]) b[y = ver[j]] ? G(x, b[y]) : F(x, y);
if (f[x] > i) d[i+1]++, d[f[x]]--;
if (g[x] <= i) d[i+1]++;
}
rep(i, 1, n) f[i] = -1;
rep(i, 1, len) for (int j = he[c[i]], y; j; j = ne[j]) y = ver[j], f[y] = max(f[y], i);
rep(id, 1, n-len) for (int i = he[a[id]], y; i; i = ne[i]) y = ver[i], f[y] = max(f[y], f[a[id]]);
rep(i, 1, len) if (f[c[i]] >= i) d[1]++, d[i]--;
vector<int> ans;
rep(i, 1, len) if (!(d[i] += d[i-1])) ans.eb(c[i]);
sort(ans.begin(), ans.end());
cout << ans.size() << '\n';
for (int x : ans) cout << x << ' ';
return 0;
}
[ARC076F] Exhausted?
有 \(m\) 个椅子,从左到右编号为 \(1, 2, \dots, m\),有 \(n\) 名高橋君,第 \(i\) 名高橋君必须坐在编号不在 \((l_i, r_i)\) 区间内的椅子上,请计算有最少有多少高橋君没法坐上椅子。
\(n, m \le 2 \times 10^5\)
特殊图的二分图匹配问题考虑 Hall 定理,求的是最小失配数,也就是 \(\max(0, n-m, \max \{|S|-|N(S)|\})\)。
\(N(S)\) 是若干形如 \([1, l] \cup [r, m]\) 的并集,显然也是形如 \([1,l], [r, m]\) 的。所以考虑枚举这个 \(l, r\),找到 \(S\) 的最大大小。那么所有 \(l_i \le l \land r_i \ge r\) 的都是可行的 \(i\)。于是问题变为二维数点问题,扫描线即可做到 \(\mathcal O(n \log n)\)。
注意要对 \(n-m\) 取 \(\max\) 因为 \(N(S)\) 可以为全集,此时所有 \(n\) 都选上,\(N(S) = [1, m]\)。
[AGC034D] Manhattan Max Matching
给定 \(2n\) 个球的位置 \((x_i, y_i)\),球被染成红色蓝色各 \(n\) 个。你需要将红球和蓝球两两配对,使得最后位置对的曼哈顿距离之和最大。球的位置可能重合。
\(1 \le n \le 10000\)。
简单费用流题目,由于曼哈顿距离中的绝对值本来就是对正负两种情况取最大值,把每个点拆成 \(x+y, -x-y, x-y, -x+y\) 四种情况,容易发现 \(x+y\) 只能配对 \(-x-y\),\(x-y\) 只能配对 \(-x+y\)。在中间建 \(4\) 个虚点分别表示 \(4\) 种符号情况跑费用流即可。复杂度跑 \(10^4\) 肯定是够的。
更优秀的做法考虑模拟费用流。由于这个图只有 \(5\) 个部分:
- \(S\) 源点,连向所有红球点。
- 红球点 \(R_{1\sim n}\),每个连向符号点。
- 符号点 \(F_{1 \sim 4}\),连向所有蓝球点。
- 蓝球点 \(B_{1\sim n}\),连向 \(T\) 汇点。
- \(T\) 汇点。
以 \(S,F_{1,2,3,4}, T\) 六个点为关键点考虑模拟费用流的一次增广过程。增广路中有如下四种情况:
- \(S\to R_k \to F_i\),在 \(S \to R_k\) 未满流的情况下,确定 \(R_k\) 的符号为 \(F_i\)。
- \(F_i \rightarrowtail R_k \to F_j\)(\(\rightarrowtail\) 表示一条反悔边),将 \(R_k\) 的符号由 \(F_i\) 变到 \(F_j\)。
- \(F_i \to B_k \rightarrowtail F_j\),将 \(B_k\) 的符号从 \(F_j\) 变到 \(F_i\)。
- \(F_i \to B_k \to T\),在 \(B_k \to T\) 未满流的情况下,确定 \(B_k\) 的符号为 \(F_i\)。
2.3. 两种情况本质上是一样的都是从 \(F_i \rightsquigarrow F_j\),我们开 \(3\) 类堆来维护这个事情:
- \(S \to F_i\),放入所有没定符号的 \(R_k\)。
- \(F_i \to F_j\),放入所有反悔操作,标记是 \(R\) 的反悔还是 \(B\) 的反悔。
- \(F_i \to T\),放入所有没定符号的 \(B_k\)。
总共有 \(4+4+12\) 个堆。然后我们就在 \(6\) 个点,\(20\) 条边的图上跑最长路即可,Bellman Ford 即可。特别注意延迟删除的情况,要及时 continue 掉。复杂度与增广次数有关,最多增广 \(\mathcal O(n)\) 次,所以复杂度为 \(\mathcal O(c^2n \log n)\),其中 \(c = 4\)。
[NWRRC 2023] Loops
请到原题查看。
三种形状,容易注意到确定每种形状的对应大小关系后,就可以依据大小关系跑出一个拓扑序。那么我们的目标就是构造一个有向无环图。
这个限制太松了,考虑搞的紧一点,让每个行从左到右单调递增,第一列从上到下单调递增。在此基础上添加一些限制即可做到三种形状。

跑拓扑排序即可。
James and the Chase
给定一张有向强连通图。如果一个点 \(a\) 满足它到所有点都只存在一条简单路径,那么称 \(a\) 是有趣的。如果有趣的点数超过 \(20\%\),输出所有有趣的点;否则输出
-1。\(1 \le n \le 10^5, 0 \le m \le 2 \times 10^5\),无重边无自环。
这个 \(20\%\) 诡异。实际上这是在提示我们随机化,\(\mathcal O(n)\) check 一个点是否有趣是简单的,随机选取 \(200\) 个点就可以保证判断错误的概率降低到 \(0.8^{200} \approx 4 \times 10^{-20}\)。
然后考虑找到一个有趣的点 \(a\) 后怎么快速找出所有有趣的点。由于 \(a\) 到所有点的简单路径仅有一条,所以可以发现把 \(a\) 定为根后,整个图变成一个树和若干返祖边。由于图的强连通性,所以对于每个点 \(v\) 来说,其子树内都至少有一个连向 \(v\) 的祖先的边。
一个简单的性质是 \(v\) 如果是有趣的,则子树内有且仅有 \(1\) 条返祖边。否则存在超过 \(1\) 条路径可以到达 \(\mathrm{fa}_v\)。单独把这些点拿出来考察。
猜想,一个点 \(v\) 是有趣的,当且仅当它子树内唯一一条返祖边指向的祖先也是有趣的。我们已经知道 \(a\) 是有趣的,对于每个返祖边 \(v \to u\),在 \(v \to u\) 的路径上做差分,再结合简单的性质就做完了。
Kevin and Teams
给定正整数 \(n\),你需要给出一个尽可能大的正整数 \(k\),表示在任意一个有 \(n\) 个点的无向图中,存在一种把点配成 \(k\) 对的方案,使得满足如下条件之一:
- 每一对点在原图中都存在连接两个点的边。
- 每一对点在原图中都不存在连接两个点的边。
你最多可以询问 \(n\) 次,每次询问给出 \(u, v\),交互器将回答图中是否存在边 \((u, v)\)。你需要给出这个图中的构造方案。交互器是自适应的。
\(2 \le n \le 10^5, 1 \le k \le \frac{n}{2}\)。
有点困难😢,不知道怎么想到。首先考虑估一个上界,通过各种手段得知答案为 \(\lfloor\frac{n+1}{3}\rfloor\),证明如下:
先证明答案 \(\le \lfloor\frac{n+1}{3}\rfloor\):考虑构造一个图,前 \(x\) 个点形成团,后面 \(n-x\) 为孤立点。则答案为 \(\max (\lfloor\frac{x}{2}\rfloor,\min(n-x, x)+\lfloor\frac{\max (0, n-2x)}{2} \rfloor)\),通过一系列操作可以发现这个答案最小可以达到 \(\lfloor \frac{n+1}{3} \rfloor\)。
考虑增量构造,要求是每 \(3\) 个点构成一个匹配。考虑维护一个存在于原图/反图的链。
加入一个点 \(u\),如果 \(u\) 与链顶 \(v\) 之间的边在当前链对应图中不存在(当然在链中只有 \(u, v\) 时不用管),那么就弹出链顶 \(v\) 和下一个链上节点 \(p\),将 \((u, v)\) 和 \((v, p)\) 分别放入两个图的匹配即可。每次 ⏏ 时两个图的匹配都会增加 \(1\),加入的总点数为 \(n\),设弹出了 \(x\) 次,则匹配总数为 \(\lfloor\frac{\max (0, n-3x)}{2}\rfloor + x = \max(x, \frac{n-x}{2})\),当然这个略微有点粗糙。
[GDCPC 2024] 小班课
有 \(m\) 门课程,\(n\) 名学生,每个人对于每个课程有一个喜爱程度,从最喜欢到最不喜欢分别为 \(b_{i,1},b_{i,2}, \dots, b_{i, k_i}\)(第 \(i\) 个人无法去未出现在 \(b_i\) 中的课程)。给学生排序后,按照顺序选课。每门课有一个容量 \(c_j\),学生会优先选择没有装满的课程中他最喜欢的一个去。合理构造一个方案使得选上小班课的人最多。
\(1\le n, m \le 500\)。
考虑这个匈牙利增广路状物,每次增广时只能优先选择最喜欢的边匹配。不难发现不存在新的匹配当且仅当不存在新的增广路。所以先按照这个带优先级的匈牙利算法得到最大匹配中 \(i\) 匹配 \(p_i\)。容易发现这样做了过后可以构造一个顺序使得匹配成立。每次找一个可以直接匹配上的匹配即可。复杂度 \(\mathcal O(n^2m)\)。
[AGC036D] Negative Cycle
有 \(n\) 个点,\(n-1\) 条有向边,第 \(i\) 条边连接顶点 \(i \to i+1\),边权为 \(0\),接下来将加入若干条边 \(i\to j(1 \le i, j \le n, i\neq j)\),若 \(i < j\) 则边权为 \(-1\),否则为 \(1\)。删除后面加入的边 \(i \to j\) 的代价为正整数 \(a_{i, j}\),求最小代价,使得删除这些边后的图中无负环。
\(3 \le n \le 500, 1 \le a_{i, j} \le 10^9\)。
把这个问题转化一下,变成差分约束模型,相当于对于一个数组 \(s_i\) 的约束,首先 \(s_i \ge s_{i+1}\) 是不可变的,然后对于每个 \((i, j)\) 有价值为 \(a_{i, j}\) 的限制 \(s_i-1 \ge s_j(i < j)\) 或 \(s_i+1 \ge s_j(j < i)\),令 \(a_i = s_i-s_{i+1}\),则限制可以变为 \(\sum _{k=i}^{j-1} a_k \ge 1\) 和 \(\sum _{k=j}^{i-1} a_k \le 1\),容易发现问题转变为构造一个 \(a\) 使得其满足的限制权值和最大。容易发现 构造 \(a\) 为 \(0/1\) 序列对答案无影响,那么我们 dp 就是了,记录一下前面两个 \(1\) 的位置即可做到 \(\mathcal O(n^3)\)。

浙公网安备 33010602011771号