天天看點

[SMOJ1831]小島II

看到這題,我首先想到是參考之前所做的“最優貿易”一題,但是事實上兩題的做法是大相徑庭的。

一點一點來分解題目,我們就能夠有一個比較清晰的思路。

首先,價值可能為負數,而經過結點是可以取也可以不取,那麼顯然不應該取負數。

從任意結點出發,在任意結點停止。也就是說,對于一個環,我們愛怎麼走就怎麼走。顯然最優方案,應該把一個強連通分量内權值為正數的點都要取一遍。

跟之前題目的政策類似,對于有向圖上的最優值問題,可以考慮一下:是否可以将圖縮點,然後利用拓撲圖的性質,做一些 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;
}