某个城市有n个景点,景点之间有单行道或者双行道,但是双行道的交通事故较多,所以准备把一些双行道改为单行,但是要保证所有的景点之间都可以到达。转化,有一个强连通的有向图,求去掉那些边可以使得有向图保持强连通性。首先要找出图的隔边,在有向图中,隔边是指去掉一个方向的边就会破坏整个有向图的强连通性的双向边。
定义rank表示某一个节点本身或者孩子通过回边所能达到的具有最小编号的深度前序遍历的节点的编号。注意一个节点的回边的意思是绕过父亲指向祖先。那么如果一个节点的某一个孩子的rank值大于这个节点的深度前序遍历编号,则这个节点到这个孩子的边一定是隔边,求隔边的方法是引入虚边,把有向图转变成为无向图,在求个隔边的问题上面是等价的(不知道怎么证明),把隔边删除,剩下的子图就是强连通分量,这些强连通分量具有一个特点就是不存在隔边,那么遇到的双向边就可以去掉多余的边,问题就在于如何发现某一双向边的某一个方向是多余的。
看看代码吧
#include<fstream>
#include<iostream>
#define MAXNUM 5
using namespace std;
ifstream fin("input.txt");
int f[MAXNUM];
int g[MAXNUM];
int rank[MAXNUM];
int father[MAXNUM];
int prev_cnt,post_cnt;
int a[MAXNUM][MAXNUM];
int n,m;
void find_bridge(int i){
f[i] = prev_cnt++;
rank[i] = f[i];
int j;
for(j=1;j<=n;j++){
if(a[i][j]){//存在一边
if(!f[j] ){//还没有遍历
father[j] = i;
find_bridge(j);//遍历这个节点
if(rank[j]>f[i]){//发现孩子j不可达到父亲
cout<<"bidirect edge "<<i<<" -> "<<j<<endl;
a[i][j] = 0;
a[j][i] = 0;
}
if(rank[j]<rank[i])//结算rank,表示孩子可以越过父亲指向最小序号的祖先
rank[i] = rank[j];
}else if(father[i] != j){//如果新的j的父亲不是i,表示孩子可以越过父亲
if(f[j] < rank[i])
rank[i] = f[j];
}
}
}
}
void dfs(int i){
int j;
f[i] = prev_cnt++;
for(j=1;j<=n;j++){
if(a[i][j]){//存在边
if(f[j]){//遍历过
if(g[j])//连接了另外一棵树的已经遍历完的节点,遍历完的节点肯定可以到达根
rank[i] = 1;
else if(father[i] != j ){//如果是一棵树上的一条回边
if(a[i][j] == 2)//能够从j遍历到i已经说明有一条从j到i的路了,不需要双向路
cout << i <<" -> "<<j;
if(f[j] < rank[i])
rank[i] = f[j];
}
}
else{
father[j] = i;
dfs(j);
if(rank[j]>f[i])//j到i必然为双向路,i到j的边不是隔边,i到j除了i->j以外还有其他方式因此i->j多余
cout << j <<" -> "<<i;
else{
if(a[i][j] == 2){//j已经有孩子可以到达i的头上了,就不需要j->i了。
cout << i <<" -> "<<j;
}
if(rank[j]<rank[i])
rank[i] = rank[j];
}
}
}
}
g[i] = post_cnt++;
}
void solve(){
int i;
for(i=1;i<=n;i++){
if(!f[i])
dfs(i);
}
}
int main(){
int i,x,y,z;
fin >> n >> m;
memset(a,0,sizeof(a));
for(i=0;i<n;i++){
fin >> x >> y >> z;
if(z==2)
a[x][y] = a[y][x] = 2;
else{
a[x][y] = 1;
a[y][x] = -1;
}
}
prev_cnt = 1;
find_bridge(1);
memset(f,0,sizeof(f));
prev_cnt = post_cnt = 1;
solve();
}