洛谷P3387 【模板】缩点(tarjan)

题目链接:https://www.luogu.org/problemnew/show/P3387

 

题目大意:

给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

 

题目思路:

先用tarjan求出强连通分量,然后对每一个强连通分量进行标号,并且每个强联通分量的值等于强联通分量内所有点的点权和。然后对于每条边,如果每条边连得两个点是属于不同强连通分量,就建边,从而形成一张新的图,在新的图上跑一个DFS就能得到最后的答案。

 

以下是代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#include<stack>
using namespace std;
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
const int MAXN = 2e5+5;
int x,y,n,m,dfn[MAXN],low[MAXN],vis[MAXN],ans,tot,num,belong[MAXN],cnt[MAXN],a[MAXN],du[MAXN];
vector<int>g[MAXN],p[MAXN];
stack<int>s;
void tarjan(int u){
    low[u]=dfn[u]=++tot;
    s.push(u);
    vis[u]=1;
    int len=g[u].size();
    rep(i,0,len-1){
        int v=g[u][i];
        if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
        else if(vis[v])low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        num++;
        while(1){
            int now=s.top();
            s.pop();
            cnt[num]+=a[now];
            belong[now]=num;
            vis[now]=0;
            if(now==u)break;
        }
    }
}
int dfs(int u){
    int ans=cnt[u];
    int len=p[u].size(),maxx=0;
    rep(i,0,len-1){
        maxx=max(maxx,dfs(p[u][i]));
    }
    return ans+maxx;
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        rep(i,1,n)scanf("%d",&a[i]);
        tot=0;
        ans=0;
        num=0;
        while(!s.empty())s.pop();
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(vis,0,sizeof(vis));
        memset(cnt,0,sizeof(cnt));
        memset(du,0,sizeof(du));
        memset(belong,0,sizeof(belong));
        rep(i,1,n)g[i].clear();
        rep(i,1,m){
            scanf("%d%d",&x,&y);
            g[x].push_back(y);
        }
        rep(i,1,n){
            if(!dfn[i]){
                tarjan(i);
            }
        }
        rep(i,1,num)p[i].clear();
        rep(i,1,n){
            int len=g[i].size();
            rep(j,0,len-1){
                int u=i,v=g[i][j];
                if(belong[u]!=belong[v]){
                    p[belong[u]].push_back(belong[v]);
                    du[belong[v]]++;
                }
            }
        }
        int ans=0;
        rep(i,1,num){
            if(!du[i]){
                ans=max(ans,dfs(i));
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值