DFS的运用(二分图判定、无向图的割顶和桥,双连通分量,有向图的强连通分量)
一、dfs框架:
1 vector<int>G[maxn]; //存图 2 int vis[maxn]; //节点访问标记 3 void dfs(int u) 4 { 5 vis[u] = 1; 6 PREVISIT(u); //访问节点u之前的操作 7 int d = G[u].size(); 8 for(int i = 0; i < d; i++)//枚举每条边 9 { 10 int v = G[u][i]; 11 if(!vis[v])dfs(v); 12 } 13 POSTVISIT(u); //访问节点u之后的操作 14 }
二、无向图连通分量
1 void find_cc() 2 { 3 current_cc = 0;//全局变量 连通块编号 4 memset(vis, 0, sizeof(vis)); 5 for(int u = 0; u < n; u++)if(!vis[u])//依次检查每个节点,如果未访问过,说明它属于一个新的连通分量,从该点dfs访问整个连通分量 6 { 7 current_cc++; 8 dfs(u); 9 } 10 }
三、二分图判定
调用之前,清空color数组,调用之前,先给color[u]赋值1
1 int color[maxn];//0表示未染色 1表示白色 2表示黑色 2 bool bipartite(int u) 3 { 4 for(int i = 0; i < G[u].size(); i++) 5 { 6 int v = G[u][i]; 7 if(color[u] == color[v])return false;//u v颜色一样 8 if(!color[v]) 9 { 10 color[v] = 3 - color[u];//节点u与v染不同的颜色 11 if(!bipartite(v))return false; 12 } 13 } 14 return true; 15 }
四、无向图的割点和桥
加入时间戳
int dfs_clock; void PREVISIT(int u){pre[u] = ++dfs_clock;} void POSTVISIT(int u){post[u] = ++dfs_clock;}
注意:求桥的时候注意重边
求割点和桥可以用下面求双连通分量的代码
五、无向图的双连通分量
点-双连通分量
1 struct Edge 2 { 3 int u, v; 4 Edge(){} 5 Edge(int u, int v):u(u), v(v){} 6 }; 7 int pre[maxn];//时间戳数组 8 int iscut[maxn];//割点 9 int bccno[maxn];//点-双连通分量编号 (割点的编号没有意义) 10 int dfs_clock, bcc_cnt;//时间戳 双连通分量编号 11 vector<int>G[maxn], bcc[maxn];//G存储图 bcc存储每个双连通分量的点 12 stack<Edge>S; 13 14 int dfs(int u, int fa) 15 { 16 int lowu = pre[u] = ++dfs_clock; 17 int child = 0; 18 for(int i = 0; i < G[u].size(); i++) 19 { 20 int v = G[u][i]; 21 Edge e(u, v); 22 if(!pre[v]) 23 { 24 S.push(e); 25 child++; 26 int lowv = dfs(v, u); 27 lowu = Min(lowu, lowv); 28 if(lowv >= pre[u]) 29 { 30 iscut[u] = 1; 31 bcc_cnt++; 32 bcc[bcc_cnt].clear(); 33 for(;;) 34 { 35 Edge x = S.top(); 36 S.pop(); 37 if(bccno[x.u] != bcc_cnt){bcc[bcc_cnt].push_back(x.u);bccno[x.u] = bcc_cnt;} 38 if(bccno[x.v] != bcc_cnt){bcc[bcc_cnt].push_back(x.v);bccno[x.v] = bcc_cnt;} 39 if(x.u == u && x.v == v)break; 40 } 41 } 42 } 43 else if(pre[v] < pre[u] && v != fa) 44 { 45 S.push(e); 46 lowu = Min(lowu, pre[v]); 47 } 48 } 49 if(fa < 0 && child == 1)iscut[u] = 0; 50 return lowu; 51 } 52 void find_bcc(int n)//求解点-双连通分量 53 { 54 Mem(pre); 55 Mem(iscut); 56 Mem(bccno); 57 dfs_clock = bcc_cnt = 0; 58 for(int i = 0; i < n; i++)if(!pre[i])dfs(i, -1); 59 }
边-双连通分量
1 struct Edge 2 { 3 int u, v; 4 Edge(){} 5 Edge(int u, int v):u(u), v(v){} 6 }; 7 int pre[maxn];//时间戳数组 8 int isbridge[maxm];//桥 9 int bccno[maxn];//边-双连通分量编号 (任意两个无交集) 10 int low[maxn];//low[u]为u后代可以返回的最早祖先值 11 int dfs_clock, bcc_cnt;//时间戳 双连通分量编号 12 vector<Edge>e;//存边 13 vector<int>G[maxn], bcc[maxn];//G存储图 bcc存储每个双连通分量的点 14 //G存边在e中的下标 15 void init(int n) 16 { 17 for(int i = 0; i < n; i++)G[i].clear(); 18 e.clear(); 19 } 20 void addedge(int u, int v)//双向边 21 { 22 e.push_back(Edge(u, v)); 23 G[u].push_back(e.size() - 1); 24 e.push_back(Edge(v, u)); 25 G[v].push_back(e.size() - 1);//正好满足双向边^操作 26 } 27 void dfs1(int u, int fa)//找出所有的桥 28 { 29 low[u] = pre[u] = ++dfs_clock; 30 for(int i = 0; i < G[u].size(); i++) 31 { 32 int v = e[G[u][i]].v; 33 if(!pre[v]) 34 { 35 dfs1(v, u); 36 low[u] = Min(low[u], low[v]); 37 if(low[v] > pre[u])//是桥,双向边均标记一下 38 { 39 isbridge[G[u][i]] = isbridge[G[u][i]^1] = 1; 40 } 41 } 42 else if(pre[v] < pre[u] && v != fa) 43 { 44 low[u] = Min(low[u], pre[v]); 45 } 46 } 47 } 48 void dfs2(int u)//dfs染色找边-双连通分量即可 49 { 50 pre[u] = 1;//vis数组标记 51 bccno[u] = bcc_cnt; 52 bcc[bcc_cnt].push_back(u); 53 for(int i = 0; i < G[u].size(); i++) 54 { 55 int v = e[G[u][i]].v; 56 if(isbridge[G[u][i]])continue;//不走桥 57 if(!pre[v])dfs2(v); 58 } 59 } 60 void find_bcc(int n)//求解边-双连通分量 61 { 62 Mem(pre);Mem(isbridge);Mem(bccno);Mem(low); 63 dfs_clock = bcc_cnt = 0; 64 for(int i = 0; i < n; i++)if(!pre[i])dfs1(i, -1); 65 Mem(pre); 66 for(int i = 0; i < n; i++)if(!pre[i]){bcc_cnt++; bcc[bcc_cnt].clear(); dfs2(i);} 67 }
六、有向图的强连通分量
1 vector<int>G[maxn]; 2 int pre[maxn], lowlink[maxn], sccno[maxn], dfs_clock, scc_cnt; 3 stack<int>S; 4 void dfs(int u) 5 { 6 pre[u] = lowlink[u] = ++dfs_clock; 7 S.push(u); 8 for(int i = 0; i < G[u].size(); i++) 9 { 10 int v = G[u][i]; 11 if(!pre[v]) 12 { 13 dfs(v); 14 lowlink[u] = Min(lowlink[u], lowlink[v]); 15 } 16 else if(!sccno[v]) 17 { 18 lowlink[u] = Min(lowlink[u], pre[v]); 19 } 20 } 21 if(lowlink[u] == pre[u]) 22 { 23 scc_cnt++; 24 for(;;) 25 { 26 int x = S.top(); 27 S.pop(); 28 sccno[x] = scc_cnt; 29 if(x == u)break; 30 } 31 } 32 } 33 void find_scc(int n) 34 { 35 dfs_clock = scc_cnt = 0; 36 Mem(sccno); 37 Mem(pre); 38 for(int i = 0; i < n; i++)if(!pre[i])dfs(i); 39 }
七、2-SAT问题
1 struct TwoSAT 2 { 3 int n; 4 vector<int>G[maxn * 2]; 5 bool mark[maxn * 2]; 6 int S[maxn * 2], c; 7 bool dfs(int x) 8 { 9 if(mark[x^1])return false; 10 if(mark[x])return true; 11 mark[x] = true; 12 S[c++] = x; 13 for(int i = 0; i < G[x].size(); i++) 14 if(!dfs(G[x][i]))return false; 15 return true; 16 } 17 void init(int n) 18 { 19 this->n = n; 20 for(int i = 0; i < n * 2; i++)G[i].clear(); 21 Mem(mark); 22 } 23 //x = xval or y = yval 24 void add_clause(int x, int xval, int y, int yval) 25 { 26 x = x * 2 + xval; 27 y = y * 2 + yval; 28 G[x ^ 1].push_back(y); 29 G[y ^ 1].push_back(x); 30 } 31 bool solve() 32 { 33 for(int i = 0; i < n * 2; i += 2) 34 { 35 if(!mark[i] && !mark[i + 1]) 36 { 37 c = 0; 38 if(!dfs(i)) 39 { 40 while(c > 0)mark[S[--c]] = false; 41 if(!dfs(i + 1))return false; 42 } 43 } 44 } 45 return true; 46 } 47 };
越努力,越幸运