题目描述
给定一个 个点 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数
第二行 个整数,其中第 个数 表示点 的点权。
第三至 行,每行两个整数 ,表示一条 的有向边。
输出格式
共一行,最大的点权之和。
样例 #1
样例输入 #1
2 2
1 1
1 2
2 1
样例输出 #1
2
提示
对于 的数据,,,。
思路
同最大半连通子图, 若该图是DAG, 则可以直接dp求出最长路径。 这里就直接tarjan缩点建新图, 缩点的时候把强连通分量的点权值都加上即可。
点的权值可以看做从任意一点连到该点的边的权值, 如 点a 的权值为 10, 那么所有 ?-> a
的边的权值都是10。
代码
const int N = 1e4 + 10, M = 2e5 + 10;
int n, m;
int value[N];
int h[N], hs[N], e[M], ne[M], w[M], idx;
int dfn[N], low[N], ts;
int stk[N], top;
bool instk[N];
int id[N], scnt, sccsize[N];
int f[N]; // dp最大点权之和
void add(int h[], int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ts;
stk[++top] = u, instk[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (instk[j])
low[u] = min(low[u], dfn[j]);
}
if (low[u] == dfn[u])
{
++scnt;
int y;
do
{
y = stk[top--];
instk[y] = false;
id[y] = scnt;
sccsize[scnt] += value[y];
} while (y != u);
}
}
int main()
{
mem(h, -1);
mem(hs, -1);
FasterIO;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> value[i];
while (m--)
{
int a, b;
cin >> a >> b;
add(h, a, b, value[b]);
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
//缩点建边
for (int i = 1; i <= n; i++)
for (int j = h[i]; ~j; j = ne[j])
{
int k = e[j];
int a = id[i], b = id[k];
if (a != b)
{
add(hs, a, b, sccsize[b]);
}
}
// 1-scnt 新图 做dp求最长路径
int res = 0;
for (int i = scnt; i >= 1; i--)
{
if (!f[i])
f[i] = sccsize[i];
for (int j = hs[i]; ~j; j = ne[j])
{
int k = e[j];
f[k] = max(f[k], f[i] + sccsize[k]);
}
res = max(f[i], res);
}
cout << res << endl;
return 0;
}