https://www.luogu.org/problem/P3387
题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式
共一行,最大的点权之和。
输入输出样例
输入 #1
2 2
1 1
1 2
2 1
输出 #1
2
说明/提示
n<=104,m<=105,0<=点权<=1000
算法:Tarjan缩点+DAGdp
思路:为啥要缩点,因为可以多次经过一条边或者一个点,因此若一个点属于一个环,根据贪心的思想必定要把这个环都走一遍,而 t a r j a n tarjan tarjan缩点后得到的有向无环图上的每个点其实都是一个环。然后我们就考虑在得到的图上记忆化搜索呗,把能走的路径都试一下,记录最大值。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e4+5;
const int maxm=1e5+5;
struct edge
{
int to,nxt;
}Edge[maxm];
int head_sd[maxn];
edge sd[maxm];
int head[maxn],dfn[maxn],low[maxn],Stack[maxn],ins[maxn],id[maxn];
int ind[maxn],outd[maxn];//入度和出度
int a[maxn],sum[maxn],dp[maxn];
vector<int> scc[maxn];
int n,m,tot,num,top,cnt,tol;
void addedge(int x,int y)
{
Edge[++tot].to=y,Edge[tot].nxt=head[x],head[x]=tot;
}
void addedge_sd(int x,int y)
{
sd[++tol].to=y,sd[tol].nxt=head_sd[x],head_sd[x]=tol;
}
void tarjan(int x)
{
dfn[x]=low[x]=++num;
Stack[++top]=x,ins[x]=1;
int y;
for(int i=head[x];i;i=Edge[i].nxt)
{
y=Edge[i].to;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
++cnt;
do
{
y=Stack[top--],ins[y]=0;
id[y]=cnt,scc[cnt].push_back(y);
sum[cnt]+=a[y];
}while(x!=y);
}
}
void work(int u)
{
if(dp[u])
return ;
dp[u]=sum[u];
int v;
int MAX=0;
for(int i=head_sd[u];i;i=sd[i].nxt)
{
v=sd[i].to;
if(!dp[v])
work(v);
MAX=max(MAX,dp[v]);
}
dp[u]+=MAX;
}
int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
addedge(x,y);
}
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=Edge[j].nxt)
{
y=Edge[j].to;
if(id[i]==id[y])
continue;
addedge_sd(id[i],id[y]);
}
}
int ans=0;
for(int i=1;i<=cnt;i++)
if(!dp[i])
work(i),ans=max(ans,dp[i]);
printf("%d\n",ans);
return 0;
}