割点和割边
割点
定义:在一个连通分量中存在一些关键点,如果删除它,会把这个连通分量分成两个或多个,这种点称为割点。
在一个连通分量G中,对任意一点,做DFS,能够访问到所有点,产生一棵“深搜优先生成树”T。
定理1:T的根节点s是割点,当且仅当s有两个或两个以上的孩子节点。
定理2:T中非根节点u是割点,当且仅当u存在一个孩子节点v,v及其后代都没有回退边连回u的祖先。
编程实现定理2:
设u的一个直接后代是v
1) num[u], 记录DFS对每个节点的访问顺序。
2) low[v], 记录v和v的后代能够连回的祖先num。
3) 如果low[v] >= num[u], 说明v及v的后代都没有回退边回到u的祖先,那么u就是一个割点。
4) 搜完后还要对根节点特判是否是割点。
代码实现:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <set>
#define ll long long
const int mod = 1e9+7;
using namespace std;
vector<int>edge[1005];
int vis[1005],low[1005],num[1005],gd[1005],n,sum;
void dfs(int s,int f)
{
int cnt = 0;
sum++;
low[s] = num[s] = sum;//赋值,low的初值就是num
int l = edge[s].size();
for(int i = 0; i < l; i++){
int v = edge[s][i];
if(!num[v]){
cnt++;//当前节点的孩子个数
vis[v] = 1;
dfs(v,s);
low[s] = min(low[s],low[v]);//用后代更新low值。
if(low[v] >= num[s] && s != 1) gd[s] = 1;
}
else if(num[v] < num[s] && v != f) low[s] = min(low[s],num[v]);
//判断回退边,如果回退边的点的序号比s小,那么说明v是s的祖先,更新low[s].
}
if(s == 1 && cnt >= 2){
gd[s] = 1;//特判根节点
}
}
int main()
{
while(~scanf("%d",&n) && n){
sum = 0;
int t,u,v;
memset(vis,0,sizeof(vis));
memset(low,0,sizeof(low));
memset(gd,0,sizeof(gd));
memset(num,0,sizeof(num));
while(scanf("%d",&t) && t){
char c;
while(1){
scanf("%d%c",&v,&c);
edge[t].push_back(v);
edge[v].push_back(t);
if(c == '\n') break;
}
}
dfs(1,-1);
int ans = 0;
for(int i = 1; i <= n; i++){
if(gd[i]) ans++;
}
printf("%d\n",ans);
for(int i = 1; i <= n; i++) edge[i].clear();
}
return 0;
} 注意:如果想用vis判断某个点是否已经走过,那么vis要写在for循环外面,不能写在for的if判断里面。 巧妙的判断s和t之间的关键点方法:
从起点s到终点t的路径数等于经过点v的次数,那么点v就是连接s和v的关键点,如果删除这个点,那么s和t不能连通。很好理解,也就是说s到t的所有路径都要经过点v,没有点v这些路径都不能够连接到t点。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <set>
#define ll long long
const int mod = 1e9+7;
using namespace std;
vector<int>edge[2005];
int vis[1005],lz = 0,num[1005];
int n,m;
void dfs(int s,int t)
{
if(s == t){
lz++;
for(int i = 1; i <= n; i++){
if(vis[i]){
num[i]++;
}
}
return;
}
vis[s] = 1;
int l = edge[s].size();
for(int i = 0; i < l; i++){
int v = edge[s][i];
if(!vis[v]){
dfs(v,t);
}
}
vis[s] = 0;
}
int main()
{
int u,v;
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; i++){
scanf("%d%d",&u,&v);
edge[u].push_back(v);
edge[v].push_back(u);
}
scanf("%d%d",&u,&v);
dfs(u,v);
int cnt = 0;
for(int i = 1; i <= n; i++){
if(i != u && i != v){
if(num[i] == lz) cnt++;
}
}
if(cnt) printf("%d\n",cnt);
else printf("-1\n");
return 0;
} 割边
定义:在一个连通分量中删除一条边,把这个连通分量分成了两个,这个边称为割边。
改为:
if(low[v] > num[s] && s != 1)其他程序不变,可算出割边数量。

