tarjan强连通分量缩点笔记

题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。

直接dfs做万一有环呢?所以我们要缩点,用tarjan求强联通分量,并把每个连通块看做同一个点重新建图。
tarjan是基于dfs的算法,详细的我推荐miskcoo神犇博客:http://blog.miskcoo.com/2016/07/tarjan-algorithm-strongly-connected-components

#include<bits/stdc++.h>
using namespace std;

const int MAXN=1e5+5;

int dfn[MAXN],low[MAXN],stac[MAXN],st=0,timec=0,num=0,belong[MAXN],head[MAXN],cnt=0,ans=0;
int n,m,w[MAXN],tem2,tem3,color[MAXN];
int cntc=0,headc[MAXN];
bool in[MAXN];


struct edge{
    int to,next;
}e[MAXN],ced[MAXN];


inline void add(int u,int v){e[++cnt]=(edge){v,head[u]},head[u]=cnt;}
inline void addc(int u,int v){ced[++cntc]=(edge){v,headc[u]},headc[u]=cntc;}

void tarjan(int u){//其实就是dfs 维护一个dfn,一个low 一个栈 
    in[u]=1;//元素在栈中 
    dfn[u]=low[u]=++timec;//维护时间戳和low 
    stac[++st]=u;//栈顶元素是这个(入栈) 
    for(int i=head[u];i;i=e[i].next){//dfs遍历 
        int v=e[i].to;
        if(!dfn[v]){//如果没走过 
            tarjan(v);
            if(low[v]<low[u])low[u]=low[v];//回溯更新low 
        }
        else if(in[v]&&dfn[v]<low[u]){//如果在v已经在栈中并dfn小 更新 
            low[u]=dfn[v];
        }
    }
    if(low[u]==dfn[u]){//退栈 
        int tem=0;++num;//强联通分量多一个 
        while(tem!=u){//直到退到根 
            tem=stac[st--];
            color[num]+=w[tem];
            belong[tem]=num;//n元素属于哪个强联通分量 
            in[tem]=0;//不在栈中 
        }
    }
} 

queue<int>q;
int dis[MAXN];bool vis[MAXN];
void bfs(int x){
    memset(dis,0,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[x]=color[x];
    vis[x]=1;
    q.push(x);
    while(!q.empty())
    {
        int u=q.front();q.pop();vis[u]=0;
        for(int i=headc[u];i;i=ced[i].next)
        {
            int v=ced[i].to;
            if(dis[v]<dis[u]+color[v])
            {
                dis[v]=dis[u]+color[v];
                if(!vis[v])
                {
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
    for(int i=1;i<=num;i++)ans=max(dis[i],ans);
}

int main(){
    memset(in,0,sizeof(in));
    memset(dfn,0,sizeof(dfn));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&tem2,&tem3);
        add(tem2,tem3);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i])tarjan(i);
    }
    for(int i=1;i<=n;i++){
        for(int j=head[i];j;j=e[j].next){
            int v=e[j].to;
            if(belong[i]!=belong[v]){
                addc(belong[i],belong[v]);
            }
        }
    }
    for(int i=1;i<=num;i++){
        bfs(i);
    }
    printf("%d\n",ans);
    return 0; 
}
/*
10 6
 6 9 14 19 78 45 64 87 97 16
1 3 
3 5
5 6
6 3
9 4
4 5
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值