平面图判定 题解

题目传送门

定位:2-SAT。

前置知识

平面图

平面图:存在一种或几种方法,将这个图画在平面上,除了点之外,所有边之间没有交点的图。

例如,下面这两个都是平面图,因为前一个图可以拉成后一个的样子:


平面图定理

连通平面图有几个常用的定理:

记一个连通平面图的点数为 nn,边数为 mm

  • n3n\geqslant 3 时,有 m3×n6m\leqslant 3\times n-6
  • n3n\geqslant 3 时,存在一个点的度数不小于 55

定义一个连通平面图把平面分成的块数为 kk。如上面这个平面图,k=6k=6(算上了最外圈以外的无限大的平面部分)。有:nm+k=2n-m+k=2

平面图有关的更多笔记都会在以后写上。这里先写个大概。

题解

算法

题目简述:

给定一个无向图,保证存在一条哈密顿回路。判定是否是平面图。3n200,m1043\leqslant n\leqslant 200,m\leqslant 10^4

存在一条哈密顿回路,不妨把这个回路当成一个圆,然后考虑其他的边。

现在我们的圆已经搞出来了,问题就是圆里面的各种弦有很多的相交。这并不代表圆里面有弦就不是平面图了,因为我们可以把一些边拉在外面。这里相比都能看出把 AD,AEAD,AE 拉到圆外面去,从圆周外绕过来,这就是个完美的平面图了,但是重要的是怎么让电脑知道要这么拉。

想到如果可以把所有有交点的弦提出来,那么这两个弦是不可能都在圆里了,如果一个在圆外一个在圆里显然可以,两个都在圆外,手玩一下就发现不行。因此就是:弦 aa 拉到圆外或弦 bb 拉到圆外。转化成 2-SAT 问题的解的存在性判定,迎刃而解。

题目的边数很大,不能直接枚举每条边,但是考虑到平面图的性质,因为 n3n\geqslant 3,所以 m3×n6m\leqslant 3\times n-6,如果大于这个,就一定不是平面图。提前判掉之后,mm 就是 594594 以内了。可以枚举两个相交的弦,然后跑 2-SAT。

如何判定两个弦相交呢?考虑到在一个环上,按照环的顺序顺时针(或逆时针)编号,如果一个圆按顺序放了 A,B,C,DA,B,C,D,一个弦连接到 ACAC,一个弦连接到 BDBD,就一定在内侧相交。为什么?左转插头 DP 找证明即可。

至此这道题就做完了。代码如下:

代码

#include<bits/stdc++.h>
using namespace std;
const int max_n=20005,max_m=2000006;
inline int read(){
	int x=0;bool w=0;char c=getchar();
	while(c<'0' || c>'9') w|=c=='-',c=getchar();
	while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return w?-x:x;
}
struct graph{
	int ct,hd[max_n],to[max_m],nx[max_m];
	graph(){ct=1;}

	inline void clear(){
		ct=1;
		memset(hd,0,sizeof(hd));
	}
	inline void add(int u,int v){
		nx[++ct]=hd[u],hd[u]=ct,to[ct]=v;
	}
}e;

int n,m,a[max_n],b[max_n];

stack<int> s;
bool in[max_n];
int cntdfn,tot,dfn[max_n],low[max_n],col[max_n];

inline void Tarjan(int u,int fa){ // 2-SAT
	s.push(u),in[u]=1;
	dfn[u]=low[u]=++cntdfn;
	for(register int i=e.hd[u];i;i=e.nx[i]){
		int v=e.to[i];
		if(!dfn[v]){
			Tarjan(v,u);
			low[u]=min(low[u],low[v]);
		}
		else if(in[v])
			low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		int v=u;
		++tot;
		do{
			v=s.top();s.pop();
			in[v]=0,
			col[v]=tot;
		}while(v!=u);
	}
}

struct edge{
	int u,v;
	edge(){u=v=0;}

	inline void init(int x,int y){
		u=x,v=y;
	}
}q[max_n];

#define opp(x) (x+m)
int cntid;
bool mp[max_n][max_n];

signed main(){
	int T=read();
	while(T--){
		n=read(),m=read();
		if(m>3*n-6){
			int x,y;
			for(register int i=1;i<=m;++i)
				x=read(),y=read();
			for(register int i=1;i<=n;++i)
				x=read();
			x+=y;
			puts("NO");
			continue;
		}
		cntdfn=cntid=tot=0;
		e.clear();
		while(!s.empty()) s.pop();
		memset(dfn,0,sizeof(dfn)),
		memset(low,0,sizeof(low)),
		memset(col,0,sizeof(col));

		for(register int i=1;i<=m;++i){
			int u=read(),v=read();
			mp[u][v]=mp[v][u]=1;
		}
		for(register int i=1;i<=n;++i){
			a[i]=read();
			b[a[i]]=i;
			if(i>=2)
				mp[a[i]][a[i-1]]=mp[a[i-1]][a[i]]=0;
			if(i==n)
				mp[a[1]][a[n]]=mp[a[n]][a[1]]=0;
		} // 过滤在圆上的边,得到弦
		for(register int i=2;i<=n;++i)
			for(register int j=1;j<i;++j)
				if(mp[i][j]){
					q[++cntid].init(i,j);
					mp[i][j]=mp[j][i]=0;
				}
					
		m=cntid;
		for(register int i=2;i<=m;++i)
			for(register int j=1;j<i;++j){
				int u=b[q[i].u],v=b[q[i].v];
				int x=b[q[j].u],y=b[q[j].v];
				if(u>v) swap(u,v);
				if(x>y) swap(x,y);
				if((u<x && v>x && v<y) || (v>y && u>x && u<y)){
					e.add(i,opp(j)),
					e.add(opp(i),j),
					e.add(j,opp(i)),
					e.add(opp(j),i);
				}// 一个在外一个在内
			}
		for(register int i=1;i<=m*2;++i)
			if(!dfn[i])
				Tarjan(i,i);

		bool ok=1;
		for(register int i=1;i<=m && ok;++i)
			if(col[i]==col[opp(i)])
				ok=0;
		puts(ok?"YES":"NO");
	}
	return 0;
}