[SMOJ1831]小岛II

97 篇文章 0 订阅
5 篇文章 0 订阅

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

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

跟之前题目的策略类似,对于有向图上的最优值问题,可以考虑一下:是否可以将图缩点,然后利用拓扑图的性质,做一些 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值