题目链接:https://www.luogu.org/problemnew/show/P3387
题目大意:
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
题目思路:
先用tarjan求出强连通分量,然后对每一个强连通分量进行标号,并且每个强联通分量的值等于强联通分量内所有点的点权和。然后对于每条边,如果每条边连得两个点是属于不同强连通分量,就建边,从而形成一张新的图,在新的图上跑一个DFS就能得到最后的答案。
以下是代码:
#include
#include
#include
#include
#include
#include
#include
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];
vectorg[MAXN],p[MAXN];
stacks;
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;
}