可平面性算法——路徑嵌入法
要判斷一個圖是否為平面圖,在考慮路徑嵌入法之前,先考慮其他的優化
1.根據歐拉定理:$n(點數)-e(邊數)+f(面數)=w(連通塊)+1$,而一個面最少由三個面組成,一個邊屬於兩個面,得到$3f>=2e$,又$w>=1$得到$f<=2n-4$,$e<=3n-6$
所以如果邊數超過了就直接判斷False吧(面數不太好判斷)
2.根據庫拉托夫斯基定理:一個圖的所有子圖經過縮點(將所有度為2的點去掉並連接它的相鄰兩個點)后均不為K5(五個點的完全圖)或K33(兩邊都是三個點的完全二分圖),那么這個圖就是可平面圖
由此可以得到:對於兩個可平面圖A,B,任意連接小於等於兩條從A到B的線段后得到的圖仍然是一個平面圖。(感性的理解,加入的這兩條邊並不能組成K5或者K33的任意一個部分)
這樣理論上就可以把圖分成一個個“邊三聯通分量”,當然我們只分成邊雙聯通分量就可以了。
現在開始路徑嵌入法吧!
路徑嵌入法的算法流程是這樣的:
pre:先把上面的1.2兩點優化搞了
1.取出你的邊雙聯通分量,記為G
2.在G中選出任意一個回路H,在G中去掉這個回路,將這個回路嵌入圖中,並將G分成若干個連通塊$B_1$~$B_n$,這些連通塊以邊為聯通,以已經嵌入的點作為分隔,每個連通塊的邊界(即已經嵌入的點)稱為這個連通塊的附着點
3.對所有的聯通塊計算一個值$F(B_i)$,這個值是已經嵌入的面中能夠包含$B_i$所有附着點的面的數量
4.如果有一個$F(B_i)=0$,那么就不能再嵌進去了,返回False,而對於$F=1$和$F>1$的連通塊來說,先嵌入$F=1$的,再嵌入$F>1$的,證明詳見論文
5.現在將一個連通塊嵌入圖中,首先在連通塊中找出一條路徑,這條路徑的兩個端點都是附着點,將這個路徑嵌入圖中,並將去掉這個路徑的連通塊又分為若干個連通塊,返回第三步直到所有連通塊都嵌入圖中后結束
舉個例子吧~
輸入數據:
9 20 1 2 2 3 4 5 5 6 7 8 8 9 1 4 4 7 2 5 5 8 3 6 6 9 1 5 2 4 2 6 3 5 4 8 5 7 5 9 6 8
這張圖長這樣:
任意找一回路:
分成若干個連通塊(注意連通塊是邊集):
$B_1=\{(1,5)\},B_2=\{(3,5)\},B_3=\{(2,5)\},B_4=\{(4,2)\},B_5=\{(6,2)\},B_6=\{(5,7),(4,7),(7,8),(5,8),(4,8),(5,9),(9,8),(9,6),(6,8)\}$
舉個附着點的例子吧:當前$B_1$的附着點是$\{1,5\},B_5$的附着點是$\{4,5,6\}$
當前的面(有序點集)是:
$P_1=\{4,1,2,3,6,5\},P_2=\{4,1,2,3,6,5\}$(兩個面一個是外面一個是里面,點集表示相同)
當前所有連通塊的F值都是2,所以任意取一個連通塊(例如$B_6$)
在其中取一條路徑嵌入:
彈出$B_6$,生成$B_7=\{(5,7)\},B_8=\{(4,8)\},B_9=\{(6,8)\},B_{10}=\{(5,9),(9,8),(9,6)\}$
當前的面為$P_1=\{4,1,2,3,6,5,8,4\},P_2=\{5,4,7,8\},P_3=\{4,1,2,3,6,5\}$
計算F值:$F(B_1)=2,F(B_2)=2,F(B_3)=2,F(B_4)=2,F(B_5)=2,F(B_7)=2,F(B_8)=2,F(B_9)=1,F(B_{10})=1$
所以要先嵌入$B_9$和$B_{10}$,$B_9$嵌完后就完了,$B_{10}$嵌一條路徑又會分出一個小連通塊...
最終按照程序嵌入下去直到連通塊數量為0,判斷這個圖——是平面圖!
算法復雜度分析:這個算法的復雜度和實現有着密不可分的關系,但由於代碼實在過於復雜(或者說找不到合適的數據結構來維護?),大致分析復雜度在$O(n^2)$到$O(n^3)$之間,但實際運行時由於優化很多(這些優化大多都是能夠明顯加快速度但理論分析卻省不了時間),尤其是隨機數據的表現極其良好,幾乎可以當做$O(n^2)$來看待
最后的最后,給出大常數+冗長+STL依賴症患者+詭異的實現方式代碼
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <map> #include <list> using namespace std; inline long long read(){ long long ans = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) f *= (ch == '-') ? -1 : 1, ch = getchar(); do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar(); while(isdigit(ch)); return ans * f; } const int MAXN = 205; int sta[MAXN], dfn[MAXN], low[MAXN], vis[MAXN], isEmbed[MAXN]; vector<int> Plane[MAXN<<1], book[MAXN<<1]; int PlaneNum = 1; struct Graph{ map<int, int> head; vector<int> next, last, val, att; int atp, atpPos; void clear(){ head.clear(), next.clear(), last.clear(), val.clear(), att.clear(), atp = atpPos = 0; next.push_back(0), last.push_back(0), val.push_back(0); next.push_back(0), last.push_back(0), val.push_back(0); } Graph(){clear();} void add(int x,int y){ next.push_back(head[x]), last.push_back(y), val.push_back(1), head[x] = next.size() - 1; } const bool operator < (const Graph &temp) const{ return atp < temp.atp; } }Tot; void getAtp(Graph &G){ sort(G.att.begin(), G.att.end()), G.atp = 0; for(int i=1; i<=PlaneNum; i++){ if(book[i].size() < G.att.size()) continue; int now = 0; for(int j=0; j<G.att.size(); j++){ while(now < book[i].size() - 1 && book[i][now] < G.att[j]) now++; if(book[i][now] != G.att[j]) break; else if(j == G.att.size() - 1) G.atp++, G.atpPos = i; } } } void embed(int pos){ for(int i=1; i<=sta[0]; i++) isEmbed[sta[i]] = true; int l = 0, r = Plane[pos].size() - 1; while(Plane[pos][l] != sta[1] && Plane[pos][l] != sta[sta[0]]) l++; while(Plane[pos][r] != sta[1] && Plane[pos][r] != sta[sta[0]]) r--; vector<int> temp1, temp2; for(int i=0; i<l; i++) temp1.push_back(Plane[pos][i]); if(Plane[pos][l] == sta[1]) for(int i=1; i<=sta[0]; i++) temp1.push_back(sta[i]); else for(int i=sta[0]; i>=1; i--) temp1.push_back(sta[i]); for(int i=r+1; i<Plane[pos].size(); i++) temp1.push_back(Plane[pos][i]); for(int i=r-1; i>l; i--) temp2.push_back(Plane[pos][i]); if(Plane[pos][l] == sta[1]) for(int i=1; i<=sta[0]; i++) temp2.push_back(sta[i]); else for(int i=sta[0]; i>=1; i--) temp2.push_back(sta[i]); Plane[pos] = book[pos] = temp1, ++PlaneNum; Plane[PlaneNum] = book[PlaneNum] = temp2; sort(book[pos].begin(), book[pos].end()), sort(book[PlaneNum].begin(), book[PlaneNum].end()); } bool match(int x,int goal,Graph &G){ vis[x] = true; for(int l=G.head[x]; l; l=G.next[l]){ int y = G.last[l]; if(vis[y]) continue; if(y == goal || (!isEmbed[y] && match(y, goal, G))){ G.val[l] = G.val[l^1] = 0; if(y == goal) sta[++sta[0]] = y; sta[++sta[0]] = x; return true; } } return false; } void findGraph(Graph &G,int l,Graph &ret){ int x = G.last[l], fa = G.last[l^1]; ret.add(x, fa), ret.add(fa, x), G.val[l] = G.val[l^1] = 0; if(!isEmbed[x]) for(int lk=G.head[x]; lk; lk=G.next[lk]){ if(G.val[lk]) findGraph(G, lk, ret); }else if(!vis[x]) ret.att.push_back(x), vis[x] = true; } bool Solve(list<Graph> &Lis){ if(!Lis.size()) return true; list<Graph>::iterator it = Lis.begin(); int cnt = Lis.size() - 1; while(!Lis.empty()){ Graph &Now = *it; getAtp(Now), cnt++; if(!Now.atp) return false; if(cnt == Lis.size() || Now.atp == 1){ memset(vis, 0, sizeof(vis)); sta[0] = 0, match(Now.att[0], Now.att[1], Now); embed(Now.atpPos), memset(vis, 0, sizeof(vis)); for(int j=2; j<sta[0]; j++) for(int l=Now.head[sta[j]]; l; l=Now.next[l]) if(Now.val[l]){ Graph temp; findGraph(Now, l, temp); if(!vis[sta[j]]) temp.att.push_back(sta[j]); for(int k=0; k<temp.att.size(); k++) vis[temp.att[k]] = 0; Lis.push_back(temp); } list<Graph>::iterator temp = it++; Lis.erase(temp), cnt = 0, it--; } it++; if(it == Lis.end()) it = Lis.begin(); } return true; } void Tarjan(int x,int fa,vector<Graph> &ret){ dfn[x] = low[x] = ++dfn[0]; for(int l=Tot.head[x]; l; l=Tot.next[l]){ int y = Tot.last[l]; if(y == fa) continue; if(!dfn[y]) Tarjan(y, x, ret), low[x] = min(low[x], low[y]); else low[x] = min(low[x], dfn[y]); } if(dfn[x] <= low[x]){ Graph temp; for(int l=Tot.head[x]; l; l=Tot.next[l]) if(Tot.val[l] && dfn[Tot.last[l]] > dfn[x]) findGraph(Tot, l, temp); ret.push_back(temp); } } void findCircle(Graph &G){ int x = G.last[2]; while(!vis[x]){ vis[x] = true; for(int l=G.head[x]; l; l=G.next[l]) if((l ^ 1) != sta[sta[0]]){ x = G.last[l], sta[++sta[0]] = l; break; } } int l = 1, r = sta[0]; while(G.last[sta[l] ^ 1] != x) l++; sta[0] = 0; for(int i=l; i<=r; i++) G.val[sta[i]] = G.val[sta[i] ^ 1] = 0, sta[++sta[0]] = G.last[sta[i] ^ 1]; } int main(){ int T = read(); while(T--){ int n = read(), m = read(); vector<Graph> Div; Tot.clear(); for(int i=1; i<=m; i++){ int x = read(), y = read(); if(x == y) continue; Tot.add(x, y), Tot.add(y, x); } for(int i=1; i<=n; i++) read(); if(m > 3 * n - 6 && m > 1){ printf("NO\n"); continue; } memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(isEmbed, 0, sizeof(isEmbed)); memset(vis, 0, sizeof(vis)); for(int i=1; i<=n; i++) if(!dfn[i]) Tarjan(i, -1, Div); bool flag = true; for(int i=0; i<Div.size(); i++){ if(!Div[i].head.size()) continue; sta[0] = 0, findCircle(Div[i]); Plane[1].push_back(sta[1]), Plane[1].push_back(sta[sta[0]]); embed(1); list<Graph> ret; memset(vis, 0, sizeof(vis)); for(int j=1; j<=sta[0]; j++) for(int l=Div[i].head[sta[j]]; l; l=Div[i].next[l]) if(Div[i].val[l]){ Graph temp; findGraph(Div[i], l, temp); if(!vis[sta[j]]) temp.att.push_back(sta[j]); for(int k=0; k<temp.att.size(); k++) vis[temp.att[k]] = 0; ret.push_back(temp); } flag &= Solve(ret); for(int j=1; j<=PlaneNum; j++) Plane[j].clear(), book[j].clear(); PlaneNum = 1; if(!flag) break; } if(flag) printf("YES\n"); else printf("NO\n"); } return 0; }
題目是HNOI2010d的Planar,只不過沒有讀入哈密頓回路
洛谷連接:https://www.luogu.com.cn/problem/P3209
哈密頓回路做法的時間:
路徑嵌入法的時間
總感覺時間多了不少啊。。。。。。不過想想直接尋找哈密頓回路的時間復雜度——
如果要判斷一般圖的平面性,還是選擇路徑嵌入法吧