[SMOJ1831]小岛II

来源:互联网 发布:凯聪网络摄像机软件 编辑:程序博客网 时间:2024/04/29 02:27

看到这题,我首先想到是参考之前所做的“最优贸易”一题,但是事实上两题的做法是大相径庭的。

一点一点来分解题目,我们就能够有一个比较清晰的思路。
首先,价值可能为负数,而经过结点是可以取也可以不取,那么显然不应该取负数。
从任意结点出发,在任意结点停止。也就是说,对于一个环,我们爱怎么走就怎么走。显然最优方案,应该把一个强连通分量内权值为正数的点都要取一遍。

跟之前题目的策略类似,对于有向图上的最优值问题,可以考虑一下:是否可以将图缩点,然后利用拓扑图的性质,做一些 DP,或是搜索?

因为是从任意点开始,任意点结束。在一个有向图中,相对来说,考虑当前结点的前驱结点能够到达当前结点,从而用当前结点的值去改进或更新前驱结点的值,是一种比较好的策略。同时,我们知道,如果固定从某个点开始,那么就可以看作一个确定的子问题,当它的值求得之后,无论是从哪个前驱结点到达当前结点,之后能取得的最优值都是确定的。

综上所述,就可以枚举起点,之后用记忆化搜索的方式,计算 fi 表示从结点 i 出发能取得的价值和。枚举一下起点,就可以在 O(n) 的时间内得到最优值。再算上前面的 tarjan 缩点,可以在线性时间内解决本题。

如果拓展一下,想一想是否可以用 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;}


0 0