一本通:
提高篇:
图论:
割点和桥:
1520:【 例 1】分离的路径
题意:如何把有桥图通过加边变成边双连通分量
如果叶子数(缩点后度为1的点)为1,则至少需要添加0条边;
否则为(叶子数+1)/ 2;
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+100;
int n,m,cnt=1; //cnt从1开始,保证取反边
int head[N],ver[N],nex[N],colour,top;
int dfn[N],low[N],dfstime,vis[N],du[N],col[N];
stack<int> st;
void add(int a,int b){
ver[++cnt] = b;
nex[cnt] = head[a];
head[a] = cnt;
}
void read(){
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
}
void tarjan(int x){
dfn[x] = low[x] = ++dfstime;
st.push(x);
for(int i=head[x];~i;i=nex[i]){
int y = ver[i];
if(!vis[i^1]){
vis[i] = 1;
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x],low[y]);
}else low[x] = min(low[x],dfn[y]);
}else vis[i] = 1;
}
int i;
if(low[x] == dfn[x]){//桥不可能在多个桥双连通里,而点可能在多个点双连通里
colour++;
do{
i=st.top();st.pop();
col[i] = colour; //通桥双连通染色
}while(i!=x);
}
}
void solve(){
//tarjan(1);
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(int x=1;x<=n;x++){
for(int i=head[x];~i;i=nex[i]){
if(vis[i^1]){ //树枝边,因为dfs不考虑双向边
vis[i] = 0;
int y=ver[i];
if(col[y] != col [x]){
du[col[x]]++; //找到叶子节点
du[col[y]]++;
}
}else vis[i] = 0;
}
}
int ans = 0;
for(int i=1;i<=colour;i++){
if(du[i]==1) ans++;
}
printf("%d\n",(ans+1)/2);//新建道路的数目是把原桥删去后桥双连通缩成点后 把每两个叶子上连边即可
//结论:当叶子节点=1,要+的边为0,否则为(叶子数+1)/2
}
int main(){
read();
solve();
return 0;
}
1521:【 例 2】矿场搭建
点双连通 + 分析
具体三种情况
1:特殊情况,只有一个双连通分量,割点为0,答案为
c
n
2
c_n^2
cn2。
2:一般情况,一个双连通分量若只有一个割点,在非割点处任意选择一个点建通道。
3:割点大于等于2,不需要再建立通道。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e3;
int n,m,cs,cnt,root,dfstime,bt;
int nex[N],head[N],ver[N];
int low[N],dfn[N],cut[N];
vector<int> block[N];
stack<int> st;
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void read(){
for(int i=0;i<N;i++) cut[i] = dfn[i]=low[i]=nex[i] = ver[i] = 0,head[i] = -1;
bt = dfstime = cnt = 0;
while(st.size()) st.pop();
for(int i = 1,x,y;i <= m;i ++){
scanf("%d%d",&x,&y);
n = max(n,max(x,y));//题目未给出最大点数
add(x,y);add(y,x);
}
}
void tarjan(int x,int root){
dfn[x] = low[x] = ++dfstime;
int tot = 0;
st.push(x);
for(int i=head[x];~i;i=nex[i]){
int y = ver[i];
if(!dfn[y]){
tot++;
tarjan(y,root);
low[x] = min(low[x],low[y]);
if((x==root&& tot>1) || (x!=root && dfn[x]<=low[y] )) cut[x] = 1;//求割点的两种判断
if(dfn[x] <= low[y]){
bt ++;
block[bt].clear();
int top;
do{
top = st.top();st.pop();
block[bt].push_back(top);
}while(y!=top);
block[bt].push_back(x); //重点:区分于桥双,点双的点可能多次出现在点双连通中
}
}else{
low[x] = min(low[x],dfn[y]);
}
}
}
void solve(){
ll res = 0,num = 1;
for(int i=1;i<=bt;i++){
int gdnum = 0;
int len = block[i].size();
for(int j =0;j<len;j++){
if(cut[block[i][j]]==1)
gdnum ++;
}
if(gdnum==0) res+=2,num = num*(len-1)*len/2;
else if(gdnum==1) res++,num=num*(len-1);
}
printf("%lld %lld\n",res,num);
}
int main(){
while(~scanf("%d",&m) && m){
printf("Case %d: ",++cs);
read();
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i,i);
solve();
}
return 0;
}
1522:网络
割点模板
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4,M = 1e6+7;
int n,m,ans;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void read(){
int x; char y;
memset(head,-1,sizeof head);
for(int i=0;i<N;i++)
low[i] = dfn[i] = cut[i] = 0;
dfstime = cnt = ans = 0;
string line;
while(getline(cin,line)){
stringstream ss(line);
int i=0,x,u,y;
while(ss>>u){
if(u==0) return;
if(i==0) x = u;
else{
add(x,u);add(u,x);
}
i++;
}
}
}
void tarjan(int x,int root){
dfn[x] = low[x] = ++dfstime;
int tot = 0;
for(int i=head[x];~i;i=nex[i]){
int y = ver[i];
if(!dfn[y]){
tot++;
tarjan(y,root);
low[x] = min(low[x],low[y]);
if((x==root && tot>1) || (x!=root && low[y]>= dfn[x])){
cut[x] = 1;
}
}else{
low[x] = min(low[x],dfn[y]);
}
}
}
void solve(){
for(int i=1;i <= n;i++)
if(!dfn[i]) tarjan(i,i);
for(int i=1;i <= n;i++)
if(cut[i]==1) ans ++;
printf("%d\n",ans);
}
int main(){
while(scanf("%d",&n) && n){
read();
solve();
}
return 0;
}
1523:嗅探器
找割点 + 分析
1:这个割点必须是从a到b的必经点,所以不可以是 a 或者 b。
2:另一个服务器是否在路径上,也就是b 和 a 要连通, 即 low[b] >= dfn[a]。
3:这个点是在b之前,即dfn[t o[x] ] <= dfn[b], a之后(我们从a开始就保证了)。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5,M = 1e6+7,INF = 0x3f3f3f3;
int n,m,ans=INF;
int a,b;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void read(){
scanf("%d",&n);
int x,y;
while(scanf("%d%d",&x,&y) && x+y!=0){
add(x,y);add(y,x);
}
scanf("%d%d",&a,&b);
}
void tarjan(int x,int root){
dfn[x] = low[x] = ++dfstime;
int tot = 0;
for(int i=head[x];i;i=nex[i]){
int y = ver[i];
if(!dfn[y]){
tot++;
tarjan(y,root);
low[x] = min(low[x],low[y]);
/*if(x!=a&&x!=b)//不是起点终点
if(dfn[x]<=low[y]&&dfn[y]<=dfn[b]&&low[b]>=dfn[a])
ans = min(ans,x);*/
if(low[y] >= dfn[x] && low[b] >= dfn[a] && dfn[y] <= dfn[b] && x!=a && x!=b){
ans = min(ans,x);
}
}else{
low[x] = min(low[x],dfn[y]);
}
}
}
int main(){
read();
tarjan(a,a);
if(ans!=INF) printf("%d\n",ans);
else puts("No solution");
return 0;
}
1524:旅游航道
桥模板
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5,M = 1e6+7,INF = 0x3f3f3f3;
int n,m,ans;
int tot,dfn[N],low[N],dfstime;
int cnt,head[M],nex[M],ver[M];
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void read(){
memset(head,-1,sizeof head);
for(int i=0;i<N;i++)
dfn[i] = low[i] = 0;
ans = cnt = 0;
int x,y;
for(int i = 1;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
}
int vis[N];
void tarjan(int x,int fa){
dfn[x] = low[x] = ++dfstime;
for(int i=head[x];~i;i=nex[i]){
int y = ver[i];
if(dfn[y] && y!=fa) low[x] = min(low[x],dfn[y]);
if(!dfn[y]){
tarjan(y,x);
if(dfn[x] < low[y]) //满足条件
ans++;
low[x] = min(low[x],low[y]);
}
}
}
int main(){
while(~scanf("%d%d",&n,&m) && n+m){
read();
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i,i);
printf("%d\n",ans);
}
return 0;
}
1525:电力
找割点
1:求割点后最多能产生多少分量:
这题关键点就是i == root && son==1 的情况,这种情况如果是没有割点的,但是切除根节点后任然会被分出一个节点,所以我们针对这种情况改变代码。
如果原始cut[i]=0,表示i是孤立的一点,此时cut[i]-1=-1.
如果原始cut[i]=1,表示i为根且有一个儿子,此时cut[i]-1=0.
如果原始cut[i]>=2,表示i为根且分割了>=2个儿子,此时cut[i]-1>=1.
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e4+7,M = 1e5+7;
typedef long long ll;
int n,m;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void read(){
cnt = dfstime = 0;
memset(head,-1,sizeof head);
for(int i=0;i < N;i++)
cut[i] = dfn[i] = low[i] = 0;
for(int i=1; i <= m ;i ++){
int x,y;scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
}
void tarjan(int x,int root){
dfn[x] = low[x] = ++dfstime;
int tot = 0;
for(int i=head[x];~i;i=nex[i]){
int y = ver[i];
if(!dfn[y]){
tot++;
tarjan(y,root);
low[x] = min(low[x],low[y]);
/*if((x==root && tot>1) || (x!=root && low[y]>= dfn[x])){
cut[x] ++; //x能够割出多少分量
}*/
if(low[y] >= dfn[x]) cut[x] ++;
}else {
low[x] = min(low[x],dfn[y]);
}
}
}
int main(){
while(scanf("%d%d",&n,&m) && n+m !=0){
read();
int sum = 0,ans = -10000;
for(int i=0; i < n;i++)
if(!dfn[i]) tarjan(i,i),sum++,cut[i]--;
for(int i=0;i<n;i++)
ans = max(ans,cut[i]);
printf("%d\n",ans+sum);
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7,M = 1e6+7;
typedef long long ll;
int n,m;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void read(){
scanf("%d%d",&n,&m);
for(int i=1; i <= m;i++){
int x,y;scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
}
int size[N]; ll ans[N];
void tarjan(int x,int root){
dfn[x] = low[x] = ++dfstime;
int tot = 0;
size[x] = 1;
ll sum = 0;
for(int i=head[x];i;i=nex[i]){
int y = ver[i];
if(!dfn[y]){
tot++;
tarjan(y,root);
low[x] = min(low[x],low[y]);
size[x] += size[y]; //记录x的子树节点个数
if((x==root && tot>1) || (x!=root && low[y]>= dfn[x])){
cut[x] = 1;
ans[x] += size[y]*sum; //所有子树能提供的边数
//x的子树size[y]能提供的个数*前面已经提供了同属于x子树的个数和
sum += size[y];//sum加上这颗子树点
}
}else{
low[x] = min(low[x],dfn[y]);
}
}
ans[x] += (n-1-sum)*sum+(n-1); //父亲块能提供的边数 + x自己能提供的边数
}
int main(){
read();
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i,i);
for(int i=1;i <= n;i++){
printf("%lld\n",ans[i]*2);
}
return 0;
}

本文深入解析图论中的割点与桥概念,通过多个实例讲解如何利用DFS算法识别并处理割点与桥,包括边双连通分量、点双连通分量的分析,以及割点在不同场景下的应用。
894

被折叠的 条评论
为什么被折叠?



