引入
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
思考
显然,如果选取了一个点,那么和这个点在一个强连通分量里的点都会被选取,因为点权都是正的,那么我们就找出所有的强连通分量,把一个强连通分量缩成一个点,再跑记搜就可以了
tarjan算法
之前在解决割点问题时已经介绍过这种算法了,没错,这种算法也可以求缩点,之前说过,核心是维护low和dfn数组,这里我们也是一样
显然,如果low[i]=dfn[i]那么i点一定与它的某一个儿子在同一强连通分量里,我们可以用一个栈来维护
对于单个点,本身就是一个强连通分量
找完强连通分量之后,我们重新建图,所以要把原来边的信息储存下来,如果2个点不在同一强连通分量里,就建一条边,之后就是记忆化搜索
ac代码
#include<bits/stdc++.h>
#define v a[i].to
using namespace std;
int n,m,x[100010],y[100010],head[10010],cnt,low[10010],dfn[10010],bl[10010],scc,sz[10010],f[10010],ans,sum[10010],val[10010];
stack<int>s;
struct edge{int to,next;}a[200010];
void add(int x,int y){a[++cnt]=(edge){y,head[x]},head[x]=cnt;}
void dfs(int u)
{
low[u]=dfn[u]=++cnt,s.push(u);
for(int i=head[u];i!=-1;i=a[i].next)
{
if(!low[v])dfs(v),low[u]=min(low[u],low[v]);
else if(!bl[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
scc++;
while(1)
{
sz[scc]++;
int k=s.top();
s.pop(),bl[k]=scc,sum[scc]+=val[k];
if(k==u)break;
}
}
}
void search(int u)
{
if(f[u])return;
f[u]=sum[u];
int maxx=0;
for(int i=head[u];i!=-1;i=a[i].next)
{
if(!f[a[i].to])search(a[i].to);
maxx=max(maxx,f[a[i].to]);
}
f[u]+=maxx;
}
int main()
{
memset(head,-1,sizeof(head)),scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
cnt=0;for(int i=1;i<=m;i++)scanf("%d%d",&x[i],&y[i]),add(x[i],y[i]);
cnt=0;for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);
memset(head,-1,sizeof(head)),cnt=0;
for(int i=1;i<=m;i++)if(bl[x[i]]!=bl[y[i]])add(bl[x[i]],bl[y[i]]);
for(int i=1;i<=scc;i++){if(!f[i])search(i);ans=max(ans,f[i]);}
printf("%d",ans);
return 0;
}