做到“[HNOI2010]平面图判定”这道题的时候用到了平面图的性质,于是来学学。
Reference1
Reference2
平面图的定义
若简单图 \(G=(V,E)\) 能画在平面上使得任意两条无重合顶点的边不相交,则称 \(G\) 是平面图(Planar Graph)。将 \(G\) 中由边构成的且不能再划分成更小子区域的区域称为平面图 \(G\) 的面(Face),包围这个区域的边称为面的边界(Bound),其中由平面图外部边界组成的面称为外部面或无限面(Exterior Face)。一个面 \(R\) 的边界的边数称为面的度,记为 \(\deg(R)\)。除了割边外,任何一条边都属于两个面的边界。
下面举几个例子。
Fig1. 一副非平面图。可以发现无论如何调整点与边的位置都无法让边不相交。

Fig2. 从 PPT 上盗的,关于平面图及其各个面。

若干定理
定理 1 一个有限平面图,其面的度之和等于其边数的两倍,即
根据前面的描述,定理 1 显然成立。
定理 2(欧拉公式) 任何一个凸多面体(联通平面图)满足关系式
其中 \(r\) 为图的面数。
证明:对 \(e\) 施以归纳法。对于 \(e<2\) 的情况显然成立。现在假设小于 \(e\) 的情况成立,考虑 \(e\) 条边的图 \(G\) 的情况。
- 若第 \(e\) 条边一端为原来的点,一端为一个新的点,这时整幅图增加了一个顶点与一条边,面数不变,此时结论显然仍成立。
- 若第 \(e\) 条边两端都为原来的点,则整幅图增加了一个面与一条边,顶点数不变,此时也满足结论。
综上,定理得证。
定理 2 的推论 设平面图 \(G=(V,E)\) 有 \(k\) 个连通块构成,则满足关系式
根据之前所证,这个推论是显然的。
定理 3 对于任意一个 \(v\ge 2\) 的联通平面图,满足
证明:设 \(G\) 有 \(r\) 个面,由于每个面至少由三条边组成,所以有
又由定理 1 知 \(2e\ge 3r\),代入欧拉公式消去 \(r\) 得 \(e\le 3v-6\),证毕。
这个结论相当重要,请务必牢记。
定理 3 的推论 任何联通平面图中,至少存在一个其度不超过 \(5\) 的节点。
证明没看懂不过这个推论是很弱的,因为有很多非平面图也满足这个性质。
超简单的小练习:证明 \(K_{3,3}\) 与 \(K_5\) 是非平面图(套定理 3 就完事了)。
过家家级别的定理说完了,接下来请抓紧扶好,我们要跳级了。
Kuratowski 定理
Kuratowski 定理定性地说明了平面图的本质。
在说这个定理之前,先引出一个定义:对于图 \(G=(V,E)\) 的一条边 \((u,v)\),先删去这条边,然后增加一个节点 \(w\),并连接 \((u,w),(v,w)\),这样操作后所得的图称为原图的细分图。
Fig3. 从巨佬的 blog 上盗的,细分图的例子。

Kuratowski 定理 一个图是平面图当且仅当它不包含与 \(K_{3,3}\) 或 \(K_5\) 的细分图同构的子图。
也就是说,如果 \(K_{3,3}\) 或 \(K_5\) 可以通过不断细分变成这副图,则这幅图是非平面图。(可以通过 Fig1 加深理解)
证明?没找到……并且我还看见了这个……

行吧,所以虚晃一枪 qwq
[HNOI2010]平面图判定 题解
好吧这其实才是我真正要说的……
这题还是很神的,一是有个没见过的东西,二是对图的处理属实毒瘤。
首先注意到这题 \(m\le 10000\),但 \(n\le 200\),于是可以用上面的定理 2 把 \(m\) 的范围缩小。
接下来如何判定这是个平面图呢?似乎很棘手,但注意到这道题的图有个特殊性质就是存在哈密顿圈。我们可以把这个哈密顿圈看成一个圆,除去在哈密顿圈上的边后剩下的都是圆上的弦,那么如何判断两弦相交呢?考虑对于两条弦 \((x,y),(u,v)\),其中 \(x,y,u,v\) 都是哈密顿圈上的顺序编号,如果有 \(x<u<y<v\),那么这两条弦就相交了。
于是对于每条弦,我们都要维护它的互斥关系,因为一条弦可以选择画在圆内或圆外来避免冲突,对于无法避免冲突的情况,就判定为非平面图。
如何维护互斥关系?这随便,二分图、并查集、2-SAT 心情好写哪个。(我用了并查集,好写👌
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,fa[N],X[N],Y[N],id[N];
int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
bool check()
{
for(int i=1;i<=m;++i) if(find(i)==find(i+m)) return 0;
return 1;
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) scanf("%d%d",&X[i],&Y[i]);
for(int i=1,x;i<=n;++i) scanf("%d",&x),id[x]=i;
if(m>3*n-6) {puts("NO"); continue;}
for(int i=1;i<=m<<1;++i) fa[i]=i;
for(int i=1,a,b;i<=m;++i)
{
a=id[X[i]],b=id[Y[i]];
X[i]=min(a,b),Y[i]=max(a,b);
}
for(int i=1;i<m;++i)
for(int j=i+1;j<=m;++j)
{
if(Y[i]==X[i]+1||(Y[i]==n&&X[i]==0)) continue;
if(Y[j]==X[j]+1||(Y[j]==n&&X[j]==0)) continue;
if(X[i]==X[j]||Y[i]==Y[j]||X[i]==Y[j]||X[j]==Y[i]) continue;
if((X[i]<X[j]&&Y[i]<Y[j]&&X[j]<Y[i])||
(X[j]<X[i]&&Y[j]<Y[i]&&X[i]<Y[j]))
fa[find(i)]=find(j+m),fa[find(j)]=find(i+m);
}
puts(check()?"YES":"NO");
}
return 0;
}
