看到这题,我首先想到是参考之前所做的“最优贸易”一题,但是事实上两题的做法是大相径庭的。
一点一点来分解题目,我们就能够有一个比较清晰的思路。
首先,价值可能为负数,而经过结点是可以取也可以不取,那么显然不应该取负数。
从任意结点出发,在任意结点停止。也就是说,对于一个环,我们爱怎么走就怎么走。显然最优方案,应该把一个强连通分量内权值为正数的点都要取一遍。
跟之前题目的策略类似,对于有向图上的最优值问题,可以考虑一下:是否可以将图缩点,然后利用拓扑图的性质,做一些 DP,或是搜索?
因为是从任意点开始,任意点结束。在一个有向图中,相对来说,考虑当前结点的前驱结点能够到达当前结点,从而用当前结点的值去改进或更新前驱结点的值,是一种比较好的策略。同时,我们知道,如果固定从某个点开始,那么就可以看作一个确定的子问题,当它的值求得之后,无论是从哪个前驱结点到达当前结点,之后能取得的最优值都是确定的。
综上所述,就可以枚举起点,之后用记忆化搜索的方式,计算
fi
表示从结点
i
出发能取得的价值和。枚举一下起点,就可以在
如果拓展一下,想一想是否可以用 spfa 搞呢?
本题是不太适合的。因为 spfa 的松弛里面可能出现多次取同一结点价值的情况,如果再加个状态记录显然是不现实的。所以要分析题目,各种角度考虑做法,切忌思维定式。同时还是那句话,积累解决同一类题目的常见策略。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
const int maxn = 1e5 + 100;
struct Edge { int to, next; } edge[maxn], edge1[maxn];
int cntEdge, cntEdge1;
int a[maxn], b[maxn];
int n, m;
int value[maxn], belong[maxn];
int head[maxn], head1[maxn];
void addEdge(int u, int v) {
edge[++cntEdge].to = v;
edge[cntEdge].next = head[u];
head[u] = cntEdge;
}
void addEdge1(int u, int v) {
edge1[++cntEdge1].to = v;
edge1[cntEdge1].next = head1[u];
head1[u] = cntEdge1;
}
int timeStamp;
int dfn[maxn], low[maxn];
int cntScc;
bool instack[maxn];
stack <int> st;
int val[maxn];
void tarjan(int root) {
dfn[root] = low[root] = ++timeStamp;
instack[root] = true;
st.push(root);
for (int i = head[root]; i; i = edge[i].next) {
int To = edge[i].to;
if (!dfn[To]) { tarjan(To); low[root] = min(low[root], low[To]); }
else if (instack[To]) low[root] = min(low[root], dfn[To]);
}
if (dfn[root] == low[root]) {
++cntScc;
int cur;
do {
cur = st.top(); st.pop();
instack[cur] = false;
belong[cur] = cntScc;
}
while (cur != root);
}
}
int dp[maxn];
int ans;
bool vis[maxn]; //记忆化,搜索过的标记
void dfs(int r) {
if (vis[r]) return; else vis[r] = true;
for (int i = head1[r]; i; i = edge1[i].next) {
dfs(edge1[i].to);
dp[r] = max(dp[r], dp[edge1[i].to]);
}
dp[r] += val[r];
}
int main(void) {
freopen("1831.in", "r", stdin);
freopen("1831.out", "w", stdout);
int r;
scanf("%d", &r);
while (r--) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%d", &value[i]);
memset(head, 0, sizeof head);
cntEdge = 0;
for (int i = 0; i < m; i++) {
scanf("%d%d", &a[i], &b[i]);
addEdge(a[i], b[i]);
}
timeStamp = cntScc = 0;
memset(dfn, 0, sizeof dfn);
memset(val, 0, sizeof val);
while (!st.empty()) st.pop();
memset(belong, 0, sizeof belong);
for (int i = 0; i < n; i++) if (!dfn[i]) tarjan(i);
memset(head1, 0, sizeof head1);
cntEdge1 = 0;
for (int i = 0; i < n; i++) { //缩点后重新构图
for (int j = head[i]; j; j = edge[j].next)
if (belong[i] != belong[edge[j].to]) addEdge1(belong[i], belong[edge[j].to]);
if (value[i] > 0) val[belong[i]] += value[i]; //正数才取
}
memset(dp, 0, sizeof dp);
memset(vis, false, sizeof vis);
ans = 0;
for (int i = 1; i <= cntScc; i++) { //考虑从每一个强连通分量出发
dfs(i);
ans = max(ans, dp[i]);
}
printf("%d\n", ans);
}
return 0;
}