2020安徽省大学生程序设计大赛题解——F 跳蛙出行
F 跳蛙出行
池塘里有n片荷叶排成一行,有一只青蛙在上面跳跃。但是,这只青蛙是只不同寻常的青蛙,它每跳一次,只能从一片荷叶跳到相邻的荷叶上,并且,它在两片荷叶之间,只能跳跃有限次。青蛙可以从任意荷叶出发。问它最多能跳多少次。
题目
标签
数组、二维动态规划
分析
图 F − 1 对 于 样 例 数 据 的 展 示 图F-1 \ \ \ \ 对于样例数据的展示 图F−1 对于样例数据的展示
设青蛙运动的起点为 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 对三段过程的展示
有了这三段运动,现在只需求得三段运动的跳跃次数之和的最大值。我们将其分成两个子问题:
子问题一:对于选定的起点和终点,求解过程一和过程三的最大跳跃次数之和
我们希望青蛙能尽可能的跳更多次,即对于选定的起点和终点(也就是过程二被确定),我们希望过程一和过程三的步数尽可能多。
令起点坐标为 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 m a x l 的 初 态 图F-3 \ \ \ \ maxl的初态 图F−3 maxl的初态
图 F − 4 m a x l 的 末 态 图F-4 \ \ \ \ maxl的末态 图F−4 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;
}
转载:https://blog.csdn.net/m0_46413065/article/details/113405382