题目背景
缩点+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
建原图–>tarjan求出图内所有的强联通分量–>建出DAG(缩点:即每个强联通分量视为一个新点)–>在得到的DAG上进行DP(其实是记搜…),不断更新最大点权和–>输出max值
#include<iostream>
using namespace std;
const int maxn = 10007;
const int maxm = 100007;
struct node
{
int x,y;
}E[maxm],ne[maxm];
int n,m,tm = 0,top = 0,tot = 0;
int W[maxn],dfn[maxn],low[maxn],Head[maxn],Next[maxm],S[maxn],V[maxn],sd[maxn];
int nh[maxn],nn[maxm],in[maxn],D[maxn];
void tarjan(int x)
{
dfn[x] = low[x] = ++tm;
S[++top] = x; V[x] = 1;
for (int y,i = Head[x]; i; i = Next[i])
{
y = E[i].y;
if (!dfn[y])
{
tarjan(y);
low[x] = min(low[x],low[y]);
}
else if (V[y])
{
low[x] = min(low[x],low[y]);
}
}
if (dfn[x] == low[x])
{
int y;
while (y = S[top--])
{
V[y] = 0;
sd[y] = x;
if (y==x) break;
W[x] +=W[y];
}
}
return;
}
int main()
{
cin>>n>>m;
for (int i = 1; i<=n; i++)
{
scanf("%d",&W[i]);
}
for (int i = 1; i<=m; i++)
{
scanf("%d%d",&E[i].x,&E[i].y);
Next[i] = Head[E[i].x];
Head[E[i].x] = i;
}
for (int i = 1; i<=n; i++)
{
if (!dfn[i])
{
tarjan(i);
}
}
for (int x,y,i = 1; i<=m; i++)
{
x = sd[E[i].x]; y = sd[E[i].y];
if (x!=y)
{
ne[++tot].x = x; ne[tot].y = y;
nn[tot] = nh[x];
nh[x] = tot;
in[y]++;
}
}
top = 0;
for (int i = 1; i<=n; i++)
{
if (sd[i] == i && !in[i])
{
S[++top] = i;
D[i] = W[i];
}
}
while (top)
{
int x = S[top--];
for (int y,i = nh[x]; i; i = nn[i])
{
y = ne[i].y;
D[y] = max(D[y],D[x]+W[y]);
if (--in[y] == 0)
{
S[++top] = y;
}
}
}
int ans = 0;
for (int i = 1; i<=n; i++)
{
ans = max(ans,D[i]);
}
cout<<ans<<endl;
return 0;
}