[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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
勇闯小岛源文件是Unity游戏开发引擎中的一个示例项目,该项目以小岛探险为主题,提供了一种学习和理解Unity游戏开发的方式。勇闯小岛源文件中包含了游戏所需的各种资源、脚本和场景,可以用作学习和实践Unity开发的素材。 在勇闯小岛源文件中,我们可以看到游戏场景中包括了小岛、海洋、树木、动物和玩家角色等元素。这些元素都是通过Unity的资源导入功能导入到项目中的,可以对其进行编辑、调整和组合,以创建出一个完整的游戏世界。 此外,勇闯小岛源文件中还包含了游戏所需的脚本文件。这些脚本文件使用了C#编程语言,用来实现游戏的逻辑和功能。通过查看和编辑这些脚本文件,我们可以深入了解游戏的运作原理,并可以进行自定义和扩展。 勇闯小岛源文件作为Unity的示例项目,不仅可以帮助初学者快速入门Unity游戏开发,还可以帮助开发者学习和理解一些常用的开发技术和工作流程。在源文件中,我们可以学习到如何导入和管理资源、如何构建游戏场景、如何编写脚本以及如何进行游戏测试和发布等等。 总之,勇闯小岛源文件是一个可以帮助我们学习和掌握Unity游戏开发技术的重要资源,通过研究和实践这个示例项目,我们可以不断提升自己的开发能力,并可以将所学应用到自己的游戏开发项目中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值