news 2026/9/15 18:53:18

OI Wiki 弦图:如何判定弦图并利用其性质求解问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OI Wiki 弦图:如何判定弦图并利用其性质求解问题

OI Wiki 弦图:如何判定弦图并利用其性质求解问题

【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki

OI Wiki 图论部分的 弦图 文档回答了一个具体任务:给定一个无向图,先判断它是否为弦图,如果是,则借助完美消除序列在 O(n+m) 时间复杂度内求出极大团、色数/团数、最大独立集和最小团覆盖。这些内容之所以有用,是因为很多在一般图上 NP-Hard 的问题在弦图上都有线性时间复杂度的算法,所以"先判定、再利用性质"是一条可落地的解题路径。

前提条件:输入是无向图,记点数为 n、边数为 m。下文代码均为该文档给出的 C++ 参考实现片段(依赖G邻接表、p序号数组、rnk秩数组等原实现中的全局变量),用于展示算法核心逻辑,不是可直接编译运行的完整程序。

判定弦图需要掌握的三个概念

判定算法建立在这三个定义之上:

  • 单纯点:设 N(x) 为与点 x 相邻的点集,若 {x}+N(x) 的导出子图为一个团,则 x 为单纯点。
  • 完美消除序列:v₁, v₂, …, vₙ 是 1…n 的一个排列,满足每个 vᵢ 在 {vᵢ, vᵢ₊₁, …, vₙ} 的导出子图中为单纯点。
  • 核心判据(Lemma 8):一个无向图是弦图,当且仅当它存在完美消除序列。

判定任务由此转化为两件事:求出候选序列(MCS 算法),再验证该序列是否为完美消除序列。

基线方法:反复删除单纯点

文档先给出朴素算法,适合先理解判定原理:

  1. 每次找到一个单纯点 v,将其加入完美消除序列;
  2. 将点 v 与其相邻的边从图上删除;
  3. 重复上述过程:若所有点都被删除,则原图是弦图,且已求得一个完美消除序列;若图上不存在单纯点,则原图不是弦图。

时间复杂度 O(n⁴),只适合作为理解基线或极小规模图上的做法,不作为主路径。

主路径:用最大势算法(MCS)在 O(n+m) 内求序列

最大势算法(Maximum Cardinality Search)是文档给出的主路径:

  1. 逆序给结点编号,即按从 n 到 1 的顺序给点标号;
  2. 设 labelₓ 表示第 x 个点与多少个已经标号的点相邻,每次选择 label 值最大的未标号结点进行标号;
  3. 用链表维护对于每个 i,满足 labelₓ=i 的 x。

由于每条边对 Σ labelᵢ 的贡献最多是 2,时间复杂度 O(n+m)。文档中 MCS 的核心循环如下(原实现片段,h/deg/nxt/lst为按 label 值分桶的链表结构,tf记录已标号点,cur为当前标号位置):

while (cur) { p[cur] = h[nww]; rnk[p[cur]] = cur; h[nww] = nxt[h[nww]]; lst[h[nww]] = 0; lst[p[cur]] = nxt[p[cur]] = 0; tf[p[cur]] = true; for (vector<int>::iterator it = G[p[cur]].begin(); it != G[p[cur]].end(); it++) if (!tf[*it]) { if (h[deg[*it]] == *it) h[deg[*it]] = nxt[*it]; nxt[lst[*it]] = nxt[*it]; lst[nxt[*it]] = lst[*it]; lst[*it] = nxt[*it] = 0; deg[*it]++; nxt[*it] = h[deg[*it]]; lst[h[deg[*it]]] = *it; h[deg[*it]] = *it; } cur--; if (h[nww + 1]) nww++; while (nww && !h[nww]) nww--; }

注意:原图可能不是弦图,此时 MCS 求出的序列一定不是完美消除序列,所以不能到此为止,必须接着验证序列本身。

验证:求出的序列是不是完美消除序列

  • 朴素算法:根据定义,依次检查序列上每个 vᵢ 在 {vᵢ,…,vₙ} 中与 vᵢ 相邻的点是否构成团,时间复杂度 O(nm)。
  • 优化算法:设 vᵢ 在 {vᵢ,…,vₙ} 中相邻的点按序列下标从小到大为 {v_{c₁},…,v_{c_k}},则只需判断 v_{c₁} 与其他点是否直接连通即可,时间复杂度 O(n+m)。

文档给出的优化验证代码(st[s[1]]为原实现中 s[1] 的相邻点集合,s[1]始终保存 rnk 最小的邻居,即序列中最早出现的邻居):

jud = true; for (int i = 1; i <= n; i++) { cur = 0; for (vector<int>::iterator it = G[p[i]].begin(); it != G[p[i]].end(); it++) if (rnk[p[i]] < rnk[*it]) { s[++cur] = *it; if (rnk[s[cur]] < rnk[s[1]]) swap(s[1], s[cur]); } for (int j = 2; j <= cur; j++) if (!st[s[1]].count(s[j])) { jud = false; break; } } if (!jud) printf("Imperfect\n"); else printf("Perfect\n");

这就是整条判定链的验证方式:jud全程为 true、输出Perfect,说明序列是完美消除序列,原图是弦图;任一检查失败、输出Imperfect,则原图不是弦图。至此,弦图判定问题在 O(n+m) 时间复杂度内解决。

确认为弦图后,从完美消除序列读取各性质

判定通过之后,序列 p 已经可以当作后续一切计算的基础。以下均为文档给出的线性时间做法。

求所有极大团

弦图的极大团一定为 {x}+N(x)(这里 N(x) 指与 x 相邻且在完美消除序列上位于 x 之后的点),弦图最多有 n 个极大团。判断 {x}+N(x) 是否极大:设 A={x}+N(x)、B={y}+N(y),若 A⊊B 则 A 不是极大团,此时 y 在序列上位于 x 之前;问题转化为判断是否存在 y 满足 nxt_y=x(nxt_x 为 N(x) 中序列上最靠前的点)且 |N(x)|+1 ≤ |N(y)|,时间复杂度 O(n+m)。文档代码(fst存 nxt,N存邻居个数,vis标记被包含因而非极大的团):

for (int i = 1; i <= n; i++) { cur = 0; for (vector<int>::iterator it = G[p[i]].begin(); it != G[p[i]].end(); it++) if (rnk[p[i]] < rnk[*it]) { s[++cur] = *it; if (rnk[s[cur]] < rnk[s[1]]) swap(s[1], s[cur]); } fst[p[i]] = s[1]; N[p[i]] = cur; } for (int i = 1; i <= n; i++) { if (!vis[p[i]]) ans++; if (N[p[i]] >= N[fst[p[i]]] + 1) vis[fst[p[i]]] = true; }

求色数与团数

只需数值时,直接取 |{x}+N(x)| 的最大值:

for (int i = 1; i <= n; i++) ans = max(ans, deg[i] + 1);

其中 deg[i] 为点 i 在完美消除序列上之后邻居的个数。若还需要染色方案,则按完美消除序列从后往前依次给每个点染色,给每个点染上可以染的最小颜色,时间复杂度 O(m+n);文档同时给出了 t=χ(G)=ω(G) 的正确性证明,即该方案用掉的色数恰等于团数也等于色数。

求最大独立集与最小团覆盖

最大独立集:沿完美消除序列从前往后,选择所有与已选点没有直接连边的点。设最大独立集为 {v₁,…,v_t},则团的集合 {{vᵢ}+N(vᵢ)} 就是图的最小团覆盖,两者时间复杂度均为 O(n+m):

for (int i = 1; i <= n; i++) if (!vis[p[i]]) { ans++; for (vector<int>::iterator it = G[p[i]].begin(); it != G[p[i]].end(); it++) vis[*it] = true; }

注意这段代码与极大团部分复用vis数组,实际使用时两组计算应使用各自独立的标记。

适用边界与可练的题

  • 上述结论只对弦图成立:若验证输出Imperfect,则极大团、色数等线性做法全部不可用,应退回一般图算法。
  • 朴素删除单纯点法 O(n⁴)、朴素验证 O(nm) 是文档列出的复杂度基线;文档的主路径(MCS + 优化验证 + 各性质计算)全部为 O(n+m) 级别。
  • 文档在"习题"一节列出了几道可用于练习的题:SPOJ FISHNET、洛谷 P3196 [HNOI2008] 神奇的国度、洛谷 P3852 [TJOI2007] 小朋友,可据此对照验证上面的实现。

【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 18:52:46

电气原理图转PLC梯形图的逻辑重构方法

1. 电气图到梯形图&#xff1a;不是“翻译”&#xff0c;而是“控制逻辑的重新建模”你见过最让人头疼的工控现场吗&#xff1f;不是PLC程序跑不起来&#xff0c;也不是通讯连不上——而是手捧一张密密麻麻的电气原理图&#xff0c;站在控制柜前&#xff0c;盯着继电器、接触器…

作者头像 李华
网站建设 2026/9/15 18:50:43

液压泵数字孪生预测维护:基于Simscape的建模到部署实践

简介&#xff1a;面向工业设备预测性维护与液压系统建模的工程师&#xff0c;本资源基于MATLAB Simscape构建液压泵数字孪生模型&#xff0c;并配套开发预测性维护算法&#xff0c;覆盖从组件定义、物理属性设置、系统连接、控制逻辑引入到数据采集、仿真验证、故障预测与交互界…

作者头像 李华
网站建设 2026/9/15 18:48:17

让机器学习模型准确率止跌回升:八大实战提分方法

验证集准确率停在87%已经两周了。特征加了七八个&#xff0c;没动&#xff1b;学习率试了三个量级&#xff0c;没动&#xff1b;换了个更深的机器学习模型&#xff0c;反而掉到85%。如果你也遇到过这种“怎么折腾都不涨点”的阶段&#xff0c;我建议先别急着继续试错——准确率…

作者头像 李华