题目描述
制造一把金宝剑需要 n 种原料,编号为 1 到 n,编号为 i 的原料的坚固值ai。
炼金是很讲究放入原料的顺序的,因此小 E 必须按照 1 到 n 的顺序依次将这些原料放 入炼金锅。
但是,炼金锅的容量非常有限,它最多只能容纳 w 个原料。
所幸的是,每放入一个原料之前,小 E 可以从中取出一些原料,数量不能超过 s 个。
我们定义第 i 种原料的耐久度为:放入第 i 种原料时锅内的原料总数(包括正在放入的原料)× ai,则宝剑的耐久度为所有原料的耐久度之和。
小 E 当然想让他的宝剑的耐久度尽可能得大,这样他就可以带着它进行更多的战斗,请求出耐久度的最大值。
注:这里的“放入第 i 种原料时锅内的原料总数包括正在放入锅中的原料,详细信息请见样例。
输入格式
第一行,三个整数 n,w,s。
第二行,n 个整数 a1,a2,…,an。
输出格式
一行一个整数,表示耐久度的最大值。
样例
输入#1
5 3 3
1 3 2 4 5
输出#1
40
题解
根据题意可知,放入锅中的原料只能顺序放置,且必须放入。
因此,可以设一个dp[i][j]表示放第i件原料时,且锅中的原料总数为j,能够得到的最大耐久度。
可知,状态转移方程可以写做:
dp[i][j] = max{dp[i-1][k]} + a[i] * j(j-1 <= k <= j+s-1)
详细代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 5000 + 10;
typedef long long ll;
ll n, w, s;
ll a[N];
ll dp[N][N];
int main() {
scanf("%lld%lld%lld", &n, &w, &s);
memset(dp, -INF, sizeof(dp));
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
for (int k = j - 1; k <= min(j + s - 1, w); k++)
dp[i][j] = max(dp[i][j], dp[i - 1][k] + a[i] * j);
}
}
ll ans = -INF;
for (int i = 1; i <= w; i++)
ans = max(dp[n][i], ans);
cout << ans;
return 0;
}
这个代码的时间复杂度可以达到O(nw^2)
可以考虑用单调队列优化掉max
因为 j-1 <= k <= j+s-1,所以要将 j 的循环改为从大到小。
用数组q[]模拟的队列来保存前面已经得到的dp的最大值。
维护队头,超出范围q[head] > j + s - 1,则踢出队头,head++。
维护队尾,dp[i-1][j-1]是最晚可能被踢出队头的,如果当前队尾的dp[i - 1][q[tail]],比dp[i - 1][j - 1]还要小,肯定就不如dp[i - 1][j - 1]更优了。就tail- -。
详细代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 5000 + 10;
typedef long long ll;
ll n, w, s;
ll a[N];
ll dp[N][N], q[N];
int main() {
scanf("%lld%lld%lld", &n, &w, &s);
memset(dp, -INF, sizeof(dp));
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
int l = 1, r = 1;
q[l] = w;
for (int j = w; j >= 1; j--) {
//维护队尾
while (q[l] > j + s - 1 && l <= r) l++;
//维护队头
while (dp[i - 1][q[r]] < dp[i - 1][j - 1] && l <= r) r--;
//将最近的一个(最晚可能被踢出队的)入队尾
q[++r] = j - 1;
//队头一定是可行范围内的最大的(最优)
dp[i][j] = dp[i - 1][q[l]] + a[i] * j;
}
}
ll ans = -INF;
for (int i = 1; i <= w; i++)
ans = max(dp[n][i], ans);
cout << ans;
return 0;
}