1078 字
5 分钟
【题解】P3388 【模板】割点(割顶)
NOTE之前作图的时候没考虑到深色模式,如果您觉得看不清,可以考虑切换到浅色模式,抱歉。
这题搞了我半天……
题意
这题给我们一个图,求这一个图的割点。
首先我们需要了解割点是什么,简单来说就是删除了这个点之后整个图不是连通图。
比如下图中的 就是割点*(图片来自OI Wiki)*。

方法
OI Wiki上对割点的讲解:link.
首先用dfs序给每一个节点打上时间戳*(图片来自OI Wiki)*,存在dfn[]数组中:

我们用像求强连通分量一样的 Tarjan 算法遍历每一个节点,用low[]数组存储从这个节点出发能到达的最远古的节点。
我们遍历每一个节点,然后更新节点的low[]值。更新方法:
如果搜索到的下一个节点没有在队列中,搜索完这下一个节点之后,如果这个儿子的low[]值小于自己的low[]值,就将low[]的值更新为它的儿子的low[]值。
本文中程序的v为当前节点,u为循环到的节点。
tarjan(u, v); //找下面这个点 low[v] = min(low[v], low[u]); //这个点low[v]的值就是当前low[]的值与找到的u点的low[]值的最小值还有可能搜到了还在队列中的节点(即自己的祖先),就取low[v]与num[u]的最小值。
else low[v] = min(low[v], num[u]); //low[v]就取这个点的low值与循环到的点u的dfn[u]的最小值接下来就要考虑如何发现割点并把发现的割点的数量记录下来。
当找到的点不在队列中时,先更新low[]的值,再判断如果low[u] >= num[v],即这个点不能回到祖先节点了,
并且此时所在的点不是祖先节点,而且此时所在的点没有被标记过,就 flag[v] = true, res++。
if (father != v && low[u] >= num[v] && !flag[v]) flag[v] = true, res++; //如果满足割点的条件就标记是割点,并且让答案的个数增加。另外,如果此时所在的点是祖先节点,并且它有两个及以上的儿子 (只有不在队列中的点才算儿子),而且没被标记过,就 flag[v] = true, res++。
if (father == v && child >= 2 && !flag[v]) //如果自己有两个儿子并且没有被访问过 //只有不在队列中的点才算儿子。 flag[v] = true, res++;上面的儿子可以在遍历每一个节点的时候累加。
代码
#include <cstdio>#include <vector>#include <iostream>using namespace std;
inline int read() { int X = 0; bool flag = 1; char ch = getchar(); while (ch < '0' || ch > '9') {if (ch == '-') flag = 0; ch = getchar();} while (ch >= '0' && ch <= '9') {X = (X << 1) + (X << 3) + ch - '0'; ch = getchar();} if (flag) return X; return ~ (X - 1);}
inline void write(int X) { if (X < 0) {putchar('-'); X = ~ (X - 1);} int s[50], top = 0; while (X) {s[++top] = X % 10; X /= 10;} if (!top) s[++top] = 0; while (top) putchar(s[top--] + '0'); putchar(' '); return;}
int head[20004], low[20004], num[20004];int index, res, cnt;bool flag[20004], vis[20004];
vector<int> edge[100001];
void tarjan(int v, int father) { //Tarjan算法 num[v] = low[v] = ++index; //标记dfn[]访问顺序,还有low[]的初始值 vis[v] = true; //标记这个点被访问过 int child = 0; for (auto u : edge[v]) { if (!vis[u]) { //如果u没有被访问过 child++; tarjan(u, v); //找下面这个点 low[v] = min(low[v], low[u]); //这个点low[v]的值就是当前low[]的值与找到的u点的low[]值的最小值 if (father != v && low[u] >= num[v] && !flag[v]) flag[v] = true, res++; //如果满足割点的条件就标记是割点,并且让答案的个数增加。 } else low[v] = min(low[v], num[u]); //low[v]就取这个点的low值与循环到的点u的dfn[u]的最小值 } if (father == v && child >= 2 && !flag[v]) //如果自己有两个儿子并且没有被访问过 //只有不在队列中的点才算儿子。 flag[v] = true, res++;}
int main() { int n, m; n = read(); m = read(); for (int i = 1; i <= m; i++) { int x, y; x = read(); y = read(); edge[x].push_back(y); edge[y].push_back(x); } for (int i = 1; i <= n; i++) if (!vis[i]) index = 0, tarjan(i, i); write(res); putchar('\n'); for (int i = 1; i <= n; i++) if (flag[i]) write(i); putchar('\n'); return 0;} 【题解】P3388 【模板】割点(割顶)
https://xn--24wq0n.top/posts/2021-04-20-题解_p3388_模板_割点_割顶/