题解:洛谷 P2690 Apple Catching
【题目来源】
洛谷:P2690 [USACO04NOV] Apple Catching G
【题目描述】
很少有人知道奶牛爱吃苹果。农夫约翰的农场上有两棵苹果树(编号为 \(1\) 和 \(2\) ), 每一棵树上都长满了苹果。奶牛贝茜无法摘下树上的苹果,所以她只能等待苹果 从树上落下。但是,由于苹果掉到地上会摔烂,贝茜必须在半空中接住苹果(没有人爱吃摔烂的苹果)。贝茜吃东西很快,她接到苹果后仅用几秒钟就能吃完。每一分钟,两棵苹果树其中的一棵会掉落一个苹果。贝茜已经过了足够的训练, 只要站在树下就一定能接住这棵树上掉落的苹果。同时,贝茜能够在两棵树之间 快速移动(移动时间远少于 \(1\) 分钟),因此当苹果掉落时,她必定站在两棵树其中的一棵下面。此外,奶牛不愿意不停地往返于两棵树之间,因此会错过一些苹果。苹果每分钟掉落一个,共 \(T\)(\(1 \le T \le 1000\))分钟,贝茜最多愿意移动 \(W\)(\(1 \le W \le 30\)) 次。现给出每分钟掉落苹果的树的编号,要求判定贝茜能够接住的最多苹果数。 开始时贝茜在 1 号树下。
【输入】
第一行 \(2\) 个数, \(T\) 和 \(W\)。接下来的 \(t\) 行,每行一个数,代表在时刻 \(t\) 苹果是从 \(1\) 号苹果树还是从 \(2\) 号苹果树上掉下来的。
【输出】
对于每个测试点,输出一行,一个数,为奶牛最多接到的苹果的数量。
【输入样例】
7 2
2
1
1
2
2
1
1
【输出样例】
6
【核心思想】
-
问题分析:给定 \(T\) 分钟内每分钟苹果掉落的树编号 \(a_i \in \{1, 2\}\),贝茜初始在 \(1\) 号树下,最多移动 \(W\) 次。每分钟若贝茜所在树下有苹果掉落即可接住。求最多能接住的苹果数。这是一个线性 DP 问题,关键在于状态设计需同时记录时间、已用移动次数和当前位置。
-
算法选择:
- 线性 DP(一维扩展):按时间顺序递推,状态包含"已用移动次数"和"当前位置"两个维度
- 状态转移:每分钟有两种选择——留在原地(不消耗移动次数)或从另一棵树移动过来(消耗 \(1\) 次移动次数)
-
关键步骤:
- 初始化:读取 \(T\)(总分钟数)、\(W\)(最大移动次数)、\(a[1..T]\)(每分钟掉落苹果的树编号)
- DP 状态定义:\(f[i][j][k]\) 表示前 \(i\) 分钟移动了 \(j\) 次,当前在树 \(k+1\) 下(\(k=0\) 表示树 \(1\),\(k=1\) 表示树 \(2\))接到的最多苹果数
- 初始状态:\(f[0][0][0] = 0\)(第 \(0\) 分钟在树 \(1\) 下,移动 \(0\) 次,接到 \(0\) 个),其余状态为 \(-\infty\)
- 状态转移(遍历 \(i\) 从 \(1\) 到 \(T\),\(j\) 从 \(0\) 到 \(W\)):
- 不移动:\(f[i][j][0] = f[i-1][j][0] + (a_i == 1)\),\(f[i][j][1] = f[i-1][j][1] + (a_i == 2)\)
- 移动过来(需 \(j \geq 1\)):
- 从树 \(2\) 移到树 \(1\):\(f[i][j][0] = \max(f[i][j][0], f[i-1][j-1][1] + (a_i == 1))\)
- 从树 \(1\) 移到树 \(2\):\(f[i][j][1] = \max(f[i][j][1], f[i-1][j-1][0] + (a_i == 2))\)
- 统计答案:\(ans = \max_{0 \leq j \leq W, k \in \{0,1\}} f[T][j][k]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(T \times W)\),双层循环,每层内部 \(O(1)\) 转移
- 空间复杂度:\(O(T \times W)\),三维 DP 数组(可滚动优化至 \(O(W)\))
-
线性 DP 的核心思想:
- 时间维度递推:按时间顺序处理,每分钟的状态仅依赖于前一分钟的状态,满足无后效性
- 状态设计技巧:将"当前位置"和"已用资源(移动次数)"纳入状态,将复杂的决策过程转化为状态转移
- 选择建模:每个时刻只有"不动"和"移动"两种决策,分别对应不同的前置状态,取最大值保证最优性
- 边界处理:初始位置固定在树 \(1\),通过 \(f[0][0][0] = 0\) 和其余 \(-\infty\) 确保状态合法性
- 适用于有时间序列约束、资源限制(如移动次数)的最优化问题
【算法标签】
普及 #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1005; // 最大时间上限
int t, w, ans=-1; // t:总分钟数, w:最大移动次数, ans:最多接住的苹果数
int a[N]; // a[i]:第i分钟苹果从哪棵树掉落(1或2)
int f[N][35][2]; // f[i][j][k]:前i分钟移动了j次,当前在树k+1下接到的最多苹果数
int main()
{
cin >> t >> w; // 读入总时间和最大移动次数
for (int i=1; i<=t; i++) // 读入每分钟苹果掉落的树编号
cin >> a[i];
// 初始化DP数组为负无穷,表示不可达状态
memset(f, -0x3f, sizeof(f));
f[0][0][0] = 0; // 初始状态:第0分钟在1号树下,移动0次,接到0个苹果
for (int i=1; i<=t; i++) // 外层循环:枚举每一分钟
for (int j=0; j<=w; j++) // 中层循环:枚举已使用的移动次数
{
// 情况1:不移动,继续留在当前树下
// 留在1号树下,如果当前苹果从1号树掉落则接住
f[i][j][0] = f[i-1][j][0] + (a[i]==1);
// 留在2号树下,如果当前苹果从2号树掉落则接住
f[i][j][1] = f[i-1][j][1] + (a[i]==2);
// 情况2:从另一棵树移动过来(消耗一次移动机会)
// 从2号树移动到1号树,如果当前苹果从1号树掉落则接住
if (j>=1) f[i][j][0] = max(f[i][j][0], f[i-1][j-1][1] + (a[i]==1));
// 从1号树移动到2号树,如果当前苹果从2号树掉落则接住
if (j>=1) f[i][j][1] = max(f[i][j][1], f[i-1][j-1][0] + (a[i]==2));
}
// 枚举所有可能的移动次数和最终位置,取最大值
for (int j=0; j<=w; j++)
ans = max({ans, f[t][j][0], f[t][j][1]});
cout << ans << endl; // 输出最多接住的苹果数
return 0;
}
【运行结果】
7 2
2
1
1
2
2
1
1
6
浙公网安备 33010602011771号