题目描述
给定一个 nn 个点 mm 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
思路
强连通模板题。
对于一个强连通分量,我们的选择就是把里面的点都走一遍,这肯定是最优的,也就是说一个强连通分量就相当于一个点,点权相当于这个分量里面所有点的权重和。
缩点后根据每个强连通分量的父节点重新建图,之后在DAG上跑DP。
如果有一条边从当前所在的点
u
u
u通向与其相连的点
v
v
v,那么转移方程如下,其中
w
[
v
]
w[v]
w[v]是v点的点权。
d
p
[
v
]
=
m
a
x
(
d
p
[
v
]
,
d
p
[
u
]
+
w
[
v
]
)
dp[v]=max(dp[v],dp[u]+w[v])
dp[v]=max(dp[v],dp[u]+w[v])
我们可以分别以每个点为起点跑一次,统计以此最大值,但是一个更好的方法是通过拓扑排序。
因为是DAG,我们可以通过拓扑排序确定这个图的起点,对于前一种方法,我们以每个点为起点,但是如果这个点的入度不是0,那么他肯定不是最优的,因为比他更优的是从这个点的父节点出发。所以用拓扑排序就可以避免这种情况。
链式前向星存图。数据大的情况下vector效率比较低。
#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>
using namespace std;
const int maxn = 1e4+10;
const int maxm = 1e5+10;
struct E {
int from, to, nxt;
E(){}
E(int from_, int to_, int nxt_):from(from_), to(to_), nxt(nxt_){}
} Edge[maxm], Edge2[maxm]; //要存两个图。
int tot, tot2, head[maxn], head2[maxn];
int n, m, tp, id;
int st[maxn], dfn[maxn], low[maxn], w[maxn], scc[maxn], inD[maxn], dp[maxn];
bool vis[maxn];
void tarjan(int now) {
// 基本操作
dfn[now] = low[now] = ++id;
st[tp++] = now; //节点入栈
vis[now] = 1;
for (int i = head[now]; ~i; i = Edge[i].nxt) {
int v = Edge[i].to;
if (!dfn[v]) {
tarjan(v);
low[now] = min(low[now], low[v]);
}
else if (vis[v]) {
low[now] = min(low[now], dfn[v]);
}
}
if (low[now] == dfn[now]) {
int y;
while (y = st[--tp]) {
vis[y] = 0; //别忘了出栈
scc[y] = now;
if (y == now) break;
w[now] += w[y];
}
}
}
void getNewG() {
//根据缩点的结果建立新的图
memset(head2, -1, sizeof head2);
for (int i = 0; i < m; i++) {
int u = scc[Edge[i].from], v = scc[Edge[i].to];
if (u != v) {
// 不在一个分量里,需要连边
Edge2[tot2] = E(u, v, head2[u]);
head2[u] = tot2++;
inD[v]++;
}
}
}
int topSortAndDp() {
queue<int> q;
//拓扑排序的同时DP
for (int i = 1; i <= n; i++) {
if (scc[i] == i && !inD[i]) {
q.push(i);
dp[i] = w[i];
}
}
while (!q.empty()) {
int v = q.front(); q.pop();
for (int i = head2[v]; ~i; i = Edge2[i].nxt){
int to = Edge2[i].to;
dp[to] = max(dp[to], dp[v] + w[to]);
if(--inD[to] == 0) {
q.push(to);
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, dp[i]);
return ans;
}
int main() {
memset(head, -1, sizeof head);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u,&v);
Edge[tot]=E(u, v, head[u]);
head[u] = tot++;
}
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
getNewG();
printf("%d\n", topSortAndDp());
return 0;
}