2020安徽省大学生程序设计大赛题解——F 跳蛙出行

2020安徽省大学生程序设计大赛题解——F 跳蛙出行

F 跳蛙出行

池塘里有n片荷叶排成一行,有一只青蛙在上面跳跃。但是,这只青蛙是只不同寻常的青蛙,它每跳一次,只能从一片荷叶跳到相邻的荷叶上,并且,它在两片荷叶之间,只能跳跃有限次。青蛙可以从任意荷叶出发。问它最多能跳多少次。
在这里插入图片描述
在这里插入图片描述

题目

在这里插入图片描述

标签

数组、二维动态规划

分析

图F-1
图 F − 1      对 于 样 例 数 据 的 展 示 图F-1 \ \ \ \ 对于样例数据的展示 F1    

设青蛙运动的起点为 A A A、终点为 B B B

对于青蛙任意的运动过程,我们都可以将其等价为三段运动:

  • 过程一 从 A A A点向左(右)运动并返回 A A A点。
  • 过程二 从 A A A点运动到 B B B点。
  • 过程三 从 B B B点向右(左)运动并返回 B B B点。

我们可以结合样例来理解上述描述。

图F-2
图 F − 2      对 三 段 过 程 的 展 示 图F-2 \ \ \ \ 对三段过程的展示 F2    

有了这三段运动,现在只需求得三段运动的跳跃次数之和的最大值。我们将其分成两个子问题:

子问题一:对于选定的起点和终点,求解过程一和过程三的最大跳跃次数之和

我们希望青蛙能尽可能的跳更多次,即对于选定的起点和终点(也就是过程二被确定),我们希望过程一和过程三的步数尽可能多。

令起点坐标为 i i i,终点坐标为 j j j l [ i ] l[i] l[i]表示 i i i点向左边跳,且最后回到 i i i点最多能走多少步;令 r [ j ] r[j] r[j]表示 j j j点向右边跳,且最后回到 j j j点最多能走多少步。

a [ i ] a[i] a[i]表示两点之间能跳跃的最多次数,则 l l l r r r可以用递推的方法算出。值得注意的是,如果两个点之间能够跳跃的次数是奇数次,在计算 l [ i ] l[i] l[i]时我们只能取 a [ i ] − 1 a[i]-1 a[i]1,原因是想要完成往返运动,路径上每相邻两荷叶的间隔都只能被经过偶数次。

for (int i = 1; i < n - 1; i++) {
    if (a[i] > 1) {
        l[i] = l[i - 1] + a[i] % 2 ? a[i] - 1 : a[i];
    }
}
for (int i = n - 2; i >= 0; i--) {
    if (a[i + 1] > 1) {
        r[i] = r[i + 1] + a[i + 1] % 2 ? a[i + 1] - 1 : [i + 1];
    }
}

这样,我们就找到了过程一、三这两个子问题的最优解。

过程二是由区间确定的值,我们用 d i s [ i ] dis[i] dis[i]表示从 1 1 1个荷叶开始,走到 i i i个荷叶,最多能跳多少次。则从第 i i i个荷叶跳到第 j j j个荷叶最多跳跃次数为 ∣ d i s [ j ] − d i s [ i ] ∣ \vert dis[j]-dis[i]\vert dis[j]dis[i]

我们可以这样求得 d i s [ i ] dis[i] dis[i]

dis[1] = a[1] % 2 ? a[1] : a[1] - 1;
for (int i = 2; i < n; i++) {
    dis[i] = (dis[i - 1] + a[i] % 2 ? a[i] : a[i] - 1);
}

基于上述描述,利用 l [ i ] l[i] l[i] r [ i ] r[i] r[i] d i s [ i ] dis[i] dis[i],对于任意一组起点和终点,我们都可以求出本题的一组最优解了,过程一的最优解为 l [ i ] l[i] l[i],过程二的最优解为 r [ j ] r[j] r[j],过程三的最优解为 − d i s [ i ] + d i s [ j ] ( j > i ) -dis[i]+dis[j](j > i) dis[i]+dis[j](j>i),那么整个问题的答案可用一个表达式描述。

ans = l[i] - dis[i] + r[j] + dis[j]
子问题二:选定最优解的起点和终点

现在问题在于,该如何找到最优解的起点和终点。想确定最优解对应的起点和终点,最朴素的想法是二重循环,但该方法会超时。为了优化算法,我们从三段过程对最优解的贡献出发,考虑二维动态规划可以在子问题之间无相互干扰时,利用动态规划的无后效性进行时间复杂度的简化。

这道题可以求出终点 j j j在任意位置时,找到 i i i对应的最优解,最后把两个子问题合并来得出最终答案。对于满足 { i   ∣   0 < i < = j } \{i \ \vert \ 0 < i< =j \} {i  0<i<=j}的所有 i i i,我们都可以寻找出 l [ i ] − d i s [ i ] l[i] - dis[i] l[i]dis[i]的最优解,并将其记录在 m a x l maxl maxl中,以此用一重循环解决二位动态规划问题。

我们用 a n s ans ans记录最终答案,那么每一次终点后移, m a x l maxl maxl都会斟酌 i i i在新的位置(即i=j)时是否会使得 m a x l maxl maxl获得更优解,如图所示。

图F-3
图 F − 3      m a x l 的 初 态 图F-3 \ \ \ \ maxl的初态 F3    maxl

图F-4
图 F − 4      m a x l 的 末 态 图F-4 \ \ \ \ maxl的末态 F4    maxl

求得 m a x l maxl maxl后,利用相同的方法,我们即可求解出整个问题的最优解。

long long ans = l[0] + r[0];
long long maxl = l[0];
for (int j = 1; j < n; j++) {
    int i = j;
    maxl = max(maxl, l[i] - dis[i]);
    ans = max(ans, r[j] + dis[j] + maxl);
}
参考答案(C++)
#include <bits/stdc++.h>
using namespace std;

long long l[200005], r[200005], dis[200005];
//l[i]表示i点向左边走,且最后回到i点最多能得多少分
//r[i]表示i点向右走,且最后回到i点最多能得多少
//dis[i]表示,从1号桥开始,走到i号桥,最多能得多少分
//求l[i] - dis[i] + r[j] + dis[j]

int n;
long long a[200005];

long long cal(long long x) {
    return x % 2 ? x - 1 : x;
}
set <long long> s;

int main()
{
    std::ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i < n; i++) {
        cin >> a[i];
    }

    for (int i = 1; i < n - 1; i++) {
        if (a[i] > 1) {
            l[i] = l[i - 1] + cal(a[i]);
        }
    }
    for (int i = n - 2; i >= 0; i--) {
        if (a[i + 1] > 1) {
            r[i] = r[i + 1] + cal(a[i + 1]);
        }
    }

    dis[1] = cal(a[1] - 1) + 1;
    for (int i = 2; i < n; i++) {
        dis[i] = (dis[i - 1] + cal(a[i] - 1) + 1);
    }

    long long ans = l[0] + r[0];
    long long maxl = l[0];
    for (int i = 1; i < n; i++) {
        maxl = max(maxl, l[i] - dis[i]);
        ans = max(ans, r[i] + dis[i] + maxl);
    }

    cout << ans;
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值