SPOJ TRT(奶牛零食售卖)问题DP解法及年龄处理技术咨询
零食售卖最大化收益的DP解法解析
嗨,作为DP初学者能写出递归思路已经很棒啦!你的递归逻辑完全正确,但确实存在大量重复计算的问题——比如同一个区间[x,y]会被多次调用计算,效率很低。咱们来一步步拆解这个问题的基础DP思路,重点解决年龄参数的处理问题。
核心DP状态定义
首先,我们不需要把年龄作为DP状态的一部分,因为年龄可以通过当前剩余零食的数量推导出来。
我们定义dp[x][y]表示:当盒子里剩下从第x个到第y个零食(闭区间,索引从0开始)时,能获得的最大收益。
那年龄怎么算呢?总共有N份零食,当剩下y-x+1份时,说明已经卖了N - (y-x+1)份,当前的售卖天数(年龄)就是 1 + (N - (y-x+1)) = N - (y-x+1) + 1,简化后是 N - y + x,或者也可以用区间长度计算:年龄 = N - 区间长度 + 1(区间长度是y-x+1)。
状态转移方程
对于区间[x,y],我们有两种选择:
- 从左端取零食
x,当天收益是v[x] * 当前年龄,剩下的区间[x+1,y]的最大收益是dp[x+1][y],总收益为v[x] * 年龄 + dp[x+1][y] - 从右端取零食
y,当天收益是v[y] * 当前年龄,剩下的区间[x,y-1]的最大收益是dp[x][y-1],总收益为v[y] * 年龄 + dp[x][y-1]
所以状态转移方程就是:
dp[x][y] = max( v[x] * 年龄 + dp[x+1][y], v[y] * 年龄 + dp[x][y-1] )
初始化与填充顺序
- 初始化:当区间长度为1时(
x==y),这是最后剩下的零食,会在第N天售卖,所以dp[x][x] = v[x] * N - 填充顺序:我们需要从短区间到长区间依次计算,因为长区间的结果依赖于更短的区间结果。比如先算所有长度为1的区间,再算长度为2的,直到长度为
N的区间(也就是整个零食序列)。
示例验证(以题目输入为例)
题目输入:
5 1 3 1 5 2
对应的v = [1,3,1,5,2],N=5。
- 长度为1的区间:
dp[0][0]=1*5=5,dp[1][1]=3*5=15,dp[2][2]=1*5=5,dp[3][3]=5*5=25,dp[4][4]=2*5=10 - 长度为2的区间,比如
[0,1],年龄是5-2+1=4,收益为max(1*4+15, 3*4+5)=max(19,17)=19 - 以此类推,最终计算到
dp[0][4]时,结果就是43,和题目输出一致。
完整DP代码示例(自底向上)
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int N; cin >> N; vector<int> v(N); for (int i = 0; i < N; ++i) { cin >> v[i]; } // 创建DP矩阵,dp[x][y]表示区间[x,y]的最大收益 vector<vector<int>> dp(N, vector<int>(N, 0)); // 初始化长度为1的区间 for (int i = 0; i < N; ++i) { dp[i][i] = v[i] * N; } // 枚举区间长度从2到N for (int len = 2; len <= N; ++len) { for (int x = 0; x + len <= N; ++x) { int y = x + len - 1; int age = N - len + 1; // 当前售卖的天数 dp[x][y] = max( v[x] * age + dp[x+1][y], v[y] * age + dp[x][y-1] ); } } cout << dp[0][N-1] << endl; return 0; }
为什么不用把年龄放进状态?
如果把年龄作为状态的一部分,DP会变成三维(dp[x][y][age]),空间和时间复杂度都会飙升到O(N³),对于N=2000来说完全不可行。而通过推导把年龄转化为x和y的表达式,就能把状态压缩到二维,时间复杂度降到O(N²),对于N=2000来说,400万次计算完全在合理范围内。
内容的提问来源于stack exchange,提问作者RAHUL
相关产品推荐
相关产品推荐

