您的位置:首页 > 其它

ZOJ_3795 Grouping(强连通分量 拓扑)

2015-06-23 19:36 357 查看
题目请点我

题解:

这是我的第一道强连通分量,虽然参考了别人的代码,还是很有收获。强连通分量的查找和处理是很多图论题目的第一步,所以还是很重要,这道题只是有向图的强连通处理。

这道题因为题目有讲每组关系都是不小于,那么如果出现环的话那只有一种情况,就是环上所有人都是相等的年龄,则这个环上所有的人的比较关系都是可以等价的,这就是为什么我们要先对强连通分量尽行缩点处理,将每一个强连通分量作为一个整体,之后再依据层次关系构造拓扑序,利用拓扑序找到最长的一条序列。

算法讲解

代码参考

测试数据:

4 4

1 2

2 3

3 4

4 1

5 3

1 2

2 3

3 4

9 9

5 4

4 1

1 3

3 2

2 1

1 6

6 7

7 8

8 9

5 5

1 2

2 3

3 4

4 1

5 1

代码实现:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits.h>
#include <queue>
#define MAX_N 100010
#define MAX_M 300010

using namespace std;

struct E{
int from,to,next;
bool sign;
};
int N,M;
int result;
int edgenum;//边的数目
int top,cnt,Time;//栈顶,强联通分量数目,时间轴
E edge[MAX_M];//边集
int dis[MAX_N];//拓扑序中节点的权值
int head[MAX_N];//前向星存储head数组
int belong[MAX_N];//记录某个点属于哪一个强连通分量
int indegree[MAX_N];//记录拓扑序中强连通分量入度
bool instack[MAX_N];//节点是否入栈
int DFN[MAX_N];//节点u搜索的次序编号(时间戳)
int Low[MAX_N];//节点u或u的子树能追溯到的最早的栈中节点的次序号
int Stack[MAX_N];//手写栈
vector<int> G[MAX_N];//存储拓扑序
vector<int> bcnt[MAX_N];//存储每一个强连通分量
void add(int u,int v);//前向星加边
void tarjan(int u);//查找强连通分量
void tarjan_ini(int all);
void suodian();//缩点函数,按照每个强连通分量构造拓扑序
int bfs();

int main(){
//freopen("in.txt","r",stdin);
while( scanf("%d%d",&N,&M) != EOF ){
int a,b;
edgenum = 0;
memset(head,-1,sizeof(head));
while( M-- ){
scanf("%d%d",&a,&b);
add(a,b);
}
tarjan_ini(N);
suodian();
result = bfs();
printf("%d\n",result);
}
return 0;
}

void tarjan_ini(int all){
memset(DFN,-1,sizeof(DFN));
//初始化三个指针
top = Time = cnt = 0;
for( int i = 1; i <= all; i++ ){
//查找每一个未被遍历的点的强连通分量
if( DFN[i] == -1 ){
tarjan(i);
}
}
}

//递归查找强连通分量
void tarjan(int u){
//标记时间戳
DFN[u] = Low[u] = ++Time;
//入栈,注意这里的前++
Stack[++top] = u;
instack[u] = true;

//查找所有与u相邻边,寻找强连通分量
for( int i = head[u]; i != -1; i = edge[i].next ){
E tmp = edge[i];
//未被访问节点
if( DFN[tmp.to] == -1 ){
tarjan(tmp.to);
//u或u的子树所能追溯到的最早的栈中节点
Low[u] = min(Low[u],Low[tmp.to]);
}
//已被访问且仍在栈中节点
else if( instack[tmp.to] ){
Low[u] = min(Low[u],DFN[tmp.to]);
}
}
//u为强连通分量节点,弹栈,所有元素存储到一个vector数组
if( DFN[u] == Low[u] ){
int now;
cnt++;
bcnt[cnt].clear();
do{
//弹栈,注意这里的后--
now = Stack[top--];
//标记属于哪一个强连通分量
belong[now] = cnt;
instack[now] = false;
bcnt[cnt].push_back(now);
}while( now != u );//弹到根节点截止
}
}

void suodian(){
for( int i = 1; i <= cnt; i++ ){
G[i].clear();
indegree[i] = 0;
}
//利用所有不在某一个强连通分量内部的边构造拓扑序
for( int i = 0; i < edgenum; i++ ){
int u,v;
u = belong[edge[i].from];
v = belong[edge[i].to];
if( u != v ){
indegree[v]++;
G[u].push_back(v);
}
}
}

void add(int u,int v){
E tmp = {u,v,head[u],false};
edge[edgenum] = tmp;
head[u] = edgenum++;
}

int bfs(){
int tmp;
queue<int> Q;
for( int i = 1; i <= cnt; i++ ){
if( indegree[i] == 0 ){
Q.push(i);
//初始权值为强连通分量的size
dis[i] = bcnt[i].size();
}
else{
dis[i] = 0;
}
}
while( !Q.empty() ){
int u = Q.front();
Q.pop();
int len = G[u].size();
for( int i = 0; i < len; i++ ){
int v = G[u][i];
//叠加找到最大权值
dis[v] = max(dis[v],(int)bcnt[v].size()+dis[u]);
//若该图为一个强连通图,不会进入这里更新
//tmp = max(tmp,dis[v]);
indegree[v]--;
if( indegree[v] == 0 ){
Q.push(v);
}
}
}
//得到结果
tmp = 1;
for( int i = 1; i <= cnt; i++ ){
tmp = max(tmp,dis[i]);
}
return tmp;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  强连通分量