割点 : 连通分量中一些关键结点,一旦删去,就不连通
割边 : 连通分量中一些关键边,一旦删去,就不连通
关于割点的判断 : 利用深搜优先生成树的思想dfs
1. 若u是根结点,且子树数量大于等于2,则是割点
2, 若u是非根结点,且存在子节点v,v和v的所有子节点都没有退回边连会u之前的祖先结点,则为割点。
(割边的判断只要把条件改为low[v] > num[u]那么(v, u)就为割边了)
#include<bits/stdc++.h>
int low[1010]; // low代表v和v的后代能连到的祖先的num,
int num[1010]; // num代表递推深度(即第几个背访问到)
int cnt[1010];
void dfs(int u, int fa){
low[u] = num[u] = ++dfn;
int child = 0;
lop(i, 0, Edge[u].size()){
int v = Edge[u][i];
if(!num[v]){
dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= num[u]){//
cnt[u] = 1;
}
child++;
}
else if(fa != v && num[v] < num[u])//处理回退边
low[u] = min(low[u], num[v]);
}
if(fa == -1){
if(child >= 2)
ans.push_back(u);
}
else if(cnt[u] >= 1){
ans.push_back(u);
}
}
POJ 1144 http://poj.org/problem?id=1144
割点模板题tarjan搜就完事了,要注意的是这题的读入有坑,反正我卡了好久,所有样例都能过,但是就是wa了,结果全部改完发现没问题,打算放弃了,一改输入就对了。。。。别掉坑了各位
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))
const int MAXN = 110;
int n;
bool iscut[MAXN];
int cnt = 0;
int low[MAXN], num[MAXN];
int dfn = 0;
vector<int> Edge[MAXN];
void init(){
memset(iscut, false, sizeof(iscut));
memset(low, 0, sizeof(low));
memset(num, 0, sizeof(num));
dfn = 0;
cnt = 0;
rep(i, 1, 110)
Edge[i].clear();
}
void dfs(int u, int fa){
low[u] = num[u] = ++dfn;
int child = 0;
lop(i, 0, Edge[u].size()){
int v = Edge[u][i];
if(!num[v]){
dfs(v, u);
low[u] = min(low[v], low[u]);
if(low[v] >= num[u] && u != 1){
iscut[u] = true;
}
child++;
}
else if(v != fa && num[v] < num[u])
low[u] = min(low[u], num[v]);
}
if(u == 1 && child >= 2)
iscut[u] = true;
}
int main(){
while(cin >> n && n){
getchar();
//cout << n << el;
init();
int u, v;
while(cin >> u && u){//这题的读入有坑!一定要注意
while(1){
char ch = getchar();
if(ch == '\n')
break;
cin >> v;
Edge[u].push_back(v);
Edge[v].push_back(u);
}
}
dfs(1, -1);//本题题意思已经说明图是连通的,所以搜一个点就行了
rep(i, 1, n){
if(iscut[i])
cnt++;
}
cout << cnt << el;
}
return 0;
}
POJ 1523 http://poj.org/problem?id=1523
题意
很简单就是求出所有割点以及计算出每个割点割出的连通分量(或者说是包含这个割点的点双连通分量有几个)分别为几个
思路
用tarjan算法思想,在找割点的同时记录割出的点数
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define el '\n'
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)
vector<int> Edge[1010], ans;//Edge记录边, ans记录割点
int low[1010], num[1010]; //low代表v和v的后代能连到的祖先的num, num代表递推深度(即第几个背访问到)
int cnt[1010];//记录每个点作为切除点后生成的连通分量
int dfn = 0;//时间戳
int Newnum = 0;//记录网络编号
void init(){//重置初始化
dfn = 0;
Newnum++;
ans.clear();
rep(i, 1, 1000){
cnt[i] = 0;
num[i] = 0;
Edge[i].clear();
}
}
void tarjan(int u, int fa){//tarjan算法计算每个割点可以割出的连通分量
//dfs + 找割边 + tarjan思想
low[u] = num[u] = ++dfn;
int child = 0;
lop(i, 0, Edge[u].size()){
int v = Edge[u][i];
if(!num[v]){
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= num[u]){//
cnt[u]++;
}
child++;
}
else if(fa != v && num[v] < num[u])//处理回退边
low[u] = min(low[u], num[v]);
}
if(fa == -1){
if(child >= 2) //根割点生成的连通分量 == 其子树个数
ans.push_back(u);
}
else if(cnt[u] >= 1){//非根割点生成连通分量 == 其子树个数 + 1
ans.push_back(u);
cnt[u]++;
}
}
int main(){
int u, v;
while(cin >> u && u != 0){
init();
while(1){
cin >> v;
// cout << u << " " << v << el;
Edge[u].push_back(v);
Edge[v].push_back(u);
cin >> u;
if(u == 0)
break;
}
rep(i, 1, 1000){//对每一个点都进行一次tarjan
if(Edge[i].size() && !num[i])
tarjan(i, -1);
}
printf("Network #%d\n", Newnum);
if(ans.size() == 0)
printf(" No SPF nodes\n");
else{
sort(ans.begin(), ans.end());
lop(i, 0, ans.size()){
printf(" SPF node %d leaves %d subnets\n", ans[i], cnt[ans[i]]);
}
}
cout << el;
}
}
POJ 3352 http://poj.org/submit?problem_id=3352
大致就是求把一个无向图转换为边双连通图
1. 先用dfs把low值全部标记一遍。(方法与求割点数遍历类似)
2. low值相同就是一个边双连通分量,看成一个大结点(缩点法)
3. 缩点后计算大结点的度,并计算出度为1的大结点有ans个
4.答案为(ans + 1) / 2 (其实每2个大结点要连一条边,但是要向上取整)
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))
const int MAXN = 1100;
int n, r;
bool iscut[MAXN];
int cnt = 0;
int low[MAXN], num[MAXN];
int dfn = 0;
vector<int> Edge[MAXN];
void init(){
memset(iscut, false, sizeof(iscut));
memset(low, 0, sizeof(low));
memset(num, 0, sizeof(num));
dfn = 0;
cnt = 0;
rep(i, 1, 110)
Edge[i].clear();
}
void dfs(int u, int fa){//用割点模板计算vis的值(有多少种vis值就有多少个边双连通分量)
low[u] = num[u] = ++dfn;
//int child = 0;
lop(i, 0, Edge[u].size()){
int v = Edge[u][i];
if(!num[v]){
dfs(v, u);
low[u] = min(low[v], low[u]);
// if(low[v] >= num[u] && u != 1){
// iscut[u] = true;
// }
//child++;
}
else if(v != fa && num[v] < num[u])
low[u] = min(low[u], num[v]);
}
// if(u == 1 && child >= 2)
// iscut[u] = true;
}
int tarjan(){
int ans = 0;
int degree[MAXN] = {0};
rep(i, 1, n){
lop(j, 0, Edge[i].size()){
if(low[i] != low[Edge[i][j]])//缩点法,边连通分量看成一个整体结点
degree[low[i]]++;//degree[low[Edge[i][j]]]++
//每条边在实际遍历时会遍历2次,所以度的增加只能一直加其中一个,否则会重复叠加
}
}
rep(i, 1, n){
if(degree[i] == 1)
ans++;//记录度为一的大结点
}
return (ans + 1) / 2;//所需增边数
}
int main(){
cin >> n >> r;
rep(i, 1, r){
int u, v;
cin >> u >> v;
Edge[u].push_back(v);
Edge[v].push_back(u);
}
dfs(1, -1);
cout << tarjan();
return 0;
}