[题解]P5858 Golden Sword
第一道自己想出递推公式并且成功\(AC\)的\(dp\)绿题。
题意简述
有\(n\)种原料,每个原料有一个耐久度\(a[i]\),必须按照\(1,2,…,n\)的顺序放入炼金锅。但是炼金锅的容量是有限的,只能放\(w\)个原料,所以在每次放入原料之前,都可以选择取出\(0\sim s\)个原料再放当前的原料。
放入第\(i\)个原料后,所锻造的武器的总耐久度就会增加\(len*a[i]\)(\(len\)表示当前炼金锅中原料个数)。
询问最大的“武器总耐久度”。
数据范围:
输入输出格式
输入:\(n,w,s\),而后\(n\)个整数\(a_1,a_2,…,a_n\)。
输出:一行,表示最终答案。
思路简述
用下面的样例进行演示:
7 4 2
-5 3 -1 -4 7 -6 5
输出:17
。
如图,用\(f\)作为\(dp\)数组。\(f[i][j]\)表示放了前\(i\)个,放完后锅中还有\(j\)个的最大值。
递推公式:\(f[i][j]=a[i]*j+max(f[i-1][k])(j-1\leq k\leq min(w,j+s-1))\)
例如下图求\(f[6][2]\),就是用\(max(绿色部分)+2倍的紫色部分\)。
怎么推导呢?我们发现\(dp[6][2]\)肯定可以由\(dp[5][1]\)推来的,因为\(f[5][1]\)的基础上,不拿走任何元素就直接放入第\(6\)个元素,状态就是\(f[6][2]\)。
那么\(dp[6][2]\)还能由哪些状态推来呢?别忘了每放一个原料前,我们可以拿出最多\(s=2\)个元素。所以\(f[6][2]\)自然也能通过\(f[5][2]\)(拿\(1\)个元素)、\(f[5][3]\)(拿\(2\)个元素)。
这样我们可以写出代码(见Code部分#1)。
优化
然而这份代码提交上去,面对数据更大的点会\(TLE\)。这是因为我们每求一次最大值都要遍历上一行。怎么解决这个问题呢?没错,这就是「单调队列优化\(dp\)」。
单调队列我先开个坑,到时候填。
使用\(maxx[j]\)表示当前行即\(f[i]\)中,以\(j\)结尾,往前长度为\(min(j,s+1)\)的最大值。每次求完这一行的\(f\),就处理出\(maxx\)数组供下一行使用。
这样求\(f[i][j]\)时,就用\(maxx[j+s-1]\)就好了。
此时我们发现\(f\)也可以优化空间为一维(与HDU1024 Max Sum Plus Plus有异曲同工之妙,无论是在推导过程还是优化方面),这样既优化了时间,还优化了空间,一举两得。
注意递推完,下一行最多用到\(max(i+s,w+s)\),所以维护\(maxx\)时只需要循环至\(max(i+s,w+s)\)即可。
时间复杂度\(O(nw)\),代码见#2。
Code
#1
#include<bits/stdc++.h>
#define int long long
using namespace std;
int s,w,n,a[5010];
int f[5010][5010];
signed main(){
cin>>n>>w>>s;
for(int i=1;i<=n;i++) cin>>a[i];
memset(f,0x80,sizeof f);//long long极小值
for(int i=0;i<=w;i++) f[0][i]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=w;j++){
if(j>i) break;
for(int k=j-1;k<j+s;k++){
if(k>w) break;
f[i][j]=max(f[i][j],f[i-1][k]);
}
f[i][j]+=a[i]*j;
cout<<f[i][j]<<" ";
}
cout<<endl;
}
int ans=LLONG_MIN;
for(int i=1;i<=w;i++) ans=max(ans,f[n][i]);
cout<<ans;
return 0;
}
#2
#include<bits/stdc++.h>
#define int long long
using namespace std;
int s,w,n,a[5010],maxx[5010];
int f[5010];
deque<int> q;
signed main(){
cin>>n>>w>>s;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=w;j++){
if(j>i) break;
f[j]=maxx[j+s-1]+a[i]*j;
}
q.clear();
for(int j=1;j<=w+s;j++){
if(j>i+s) break;
if(j<=i&&j<=w){//如果i个元素加完了,就只出不入
while(!q.empty()&&f[j]>=f[q.front()]) q.pop_back();
q.push_back(j);
}
if(q.front()+s+1<=j) q.pop_front();
maxx[j]=f[q.front()];
}
}
int ans=LLONG_MIN;
for(int i=1;i<=w;i++) ans=max(ans,f[i]);
cout<<ans;
return 0;
}