Hdu 4738【tanjan求无向图的桥】割边判定定理 dfn[x] < low[y]

时间:2023-03-08 16:59:46

题目:

  曹操在长江上建立了一些点,点之间有一些边连着。如果这些点构成的无向图变成了连通图,那么曹操就无敌了。刘备为了防止曹操变得无敌,就打算去摧毁连接曹操的点的桥。但是诸葛亮把所有炸弹都带走了,只留下一枚给刘备。所以刘备只能炸一条桥。

  题目给出n,m。表示有n个点,m条桥。

  接下来的m行每行给出a,b,c,表示a点和b点之间有一条桥,而且曹操派了c个人去守卫这条桥。

  现在问刘备最少派多少人去炸桥。

  如果无法使曹操的点成为多个连通图,则输出-1.

 

思路:

  就是用tarjan算法算出桥的数量,再比较哪一个的值最小。

Tips:

  注意三点:

  ①. 有重边,所以tarjan算法要处理重边。有两种处理方法,一种是先把所有的边存下,发现两点有重边的时候就只给这两个点连一条权值为无穷大的边。或者是在tarjan算法里处理重边,即使之求u或u的子树能够追溯到的最早的栈中节点的次序号时可访问父节点的次序号。

  ②. 如果无向图图本身已经有两个连通图了,就无需派人去炸桥,这时候输出0。

  ③. 如果求出来的最小权值桥的守卫人数为0时,也需要派出一个人去炸桥。

Code:

// tarjan算法求无向图的桥、边双连通分量并缩点
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include <bits/stdc++.h>
#define cls(s,h) memset(s,h,sizeof s)
using namespace std;
const int N = ;
int n, m ;
struct edge
{
int to;
int pre;
int id;
int w;
};
edge E[N*N];
int head[N],tot;
//int soldier[N][N];//第一种方法所需的的邻接矩阵
int low[N],dfn[N],ts,top,st[N],ins[N];
int minn; void init()
{
cls(head,-);
tot=;
//CLR(soldier,INF);
cls(low,);
cls(dfn,);
ts=top=;
cls(ins,);
minn=1e8;
}
inline void add(int s,int t,int w,int id)
{
E[tot].to=t;
E[tot].id=id;
E[tot].w=w;
E[tot].pre=head[s];
head[s]=tot++;
}
void tarjan(int u,int id)
{
low[u]=dfn[u]=++ts;
ins[u]=;
st[top++]=u;
int v;
for (int i=head[u]; ~i; i=E[i].pre)
{
v=E[i].to;
if(id==E[i].id)
continue;
if(!dfn[v])
{
tarjan(v,E[i].id);
low[u]=min<int>(low[v],low[u]);
if(low[v]>dfn[u])
{
int need=E[i].w;
if(need<minn)
minn=need;
}
}
else if(ins[v])
low[u]=min<int>(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
do
{
v=st[--top];
ins[v]=;
}
while (u!=v);
}
} int main()
{
while(scanf("%d%d",&n,&m)&&n+m)
{
init(); //tot = 1;
for (int i = ; i <= m; i++)
{
int x, y,z;
scanf("%d%d%d", &x, &y,&z);
add(x, y,z,i), add(y,x,z,i);
}
int k = ;
minn = 1e8;
for (int i = ; i <= n; i++)
if (!dfn[i]) tarjan(i, -),k++;
//for (int i = 2; i < tot; i += 2)
// if (bridge[i])
// printf("%d %d\n", ver[i ^ 1], ver[i]); // for (int i = 1; i <= n; i++)
// if (!c[i]) {
// ++dcc;
// dfs(i);
// }
//printf("There are %d e-DCCs.\n", dcc);
//for (int i = 1; i <= n; i++)
// printf("%d belongs to DCC %d.\n", i, c[i]); //for (int i = 2; i <= tot; i++) {
// int x = ver[i ^ 1], y = ver[i];
// if (c[x] == c[y]) continue;
// add_c(c[x], c[y]);
// }
//printf("缩点之后的森林,点数 %d,边数 %d\n", dcc, tc / 2); //for (int i = 2; i < tc; i += 2)
//printf("%d %d %d\n", vc[i ^ 1], vc[i],edge[i]);
// minn = min(minn,edge[i]);
if(k > )
minn = ;
else if(minn == )
minn = ;
else if(minn == 1e8)
minn = -;
printf("%d\n",minn); }
return ;
}
// tarjan算法求无向图的桥、边双连通分量并缩点
// 割边判定定理 dfn[x] < low[y]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include <bits/stdc++.h>
#define cls(s,h) memset(s,h,sizeof s)
using namespace std;
const int N = ;
int n, m ;
struct edge
{
int to;
int pre;
int id;
int w;
};
edge E[N*N];
int head[N],tot;
//int soldier[N][N];//第一种方法所需的的邻接矩阵
int low[N],dfn[N],ts,top,st[N],ins[N];
int minn; void init()
{
cls(head,-);
tot=;
//CLR(soldier,INF);
cls(low,);
cls(dfn,);
ts=top=;
cls(ins,);
minn=1e8;
}
inline void add(int s,int t,int w,int id)
{
E[tot].to=t;
E[tot].id=id;
E[tot].w=w;
E[tot].pre=head[s];
head[s]=tot++;
}
void tarjan(int u,int id)
{
low[u]=dfn[u]=++ts;
ins[u]=;
st[top++]=u;
int v;
for (int i=head[u]; ~i; i=E[i].pre)
{
v=E[i].to;
if(id==E[i].id)
continue;
if(!dfn[v])
{
tarjan(v,E[i].id);
low[u]=min<int>(low[v],low[u]);
if(low[v]>dfn[u])
{
//枚举每一个桥,找到最小的桥
int need=E[i].w;
if(need<minn)
minn=need; }
}
else if(ins[v])
low[u]=min<int>(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
do
{
v=st[--top];
ins[v]=;
}
while (u!=v);
}
} int main()
{
while(scanf("%d%d",&n,&m)&&n+m)
{
init(); //tot = 1;
for (int i = ; i <= m; i++)
{
int x, y,z;
scanf("%d%d%d", &x, &y,&z);
add(x, y,z,i), add(y,x,z,i);
}
int k = ;
minn = 1e8;
for (int i = ; i <= n; i++)
if (!dfn[i]) tarjan(i, -),k++;//连通块数量
if(k > )
minn = ;
else if(minn == )
minn = ;
else if(minn == 1e8)
minn = -;
printf("%d\n",minn); }
return ;
}

更新代码