ZOJ_3795 Grouping(强连通分量 拓扑)

题目请点我
题解:
这是我的第一道强连通分量,虽然参考了别人的代码,还是很有收获。强连通分量的查找和处理是很多图论题目的第一步,所以还是很重要,这道题只是有向图的强连通处理。
这道题因为题目有讲每组关系都是不小于,那么如果出现环的话那只有一种情况,就是环上所有人都是相等的年龄,则这个环上所有的人的比较关系都是可以等价的,这就是为什么我们要先对强连通分量尽行缩点处理,将每一个强连通分量作为一个整体,之后再依据层次关系构造拓扑序,利用拓扑序找到最长的一条序列。
算法讲解
代码参考
测试数据:

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值