小 E { \mathrm{E}} E不幸在一场战斗中失去了他的金宝剑。
制造一把金宝剑需要 nn 种原料,编号为 11 到 nn,编号为 ii 的原料的坚固值为 a i {a_i} ai.
炼金是很讲究放入原料的顺序的,因此小 E {\mathrm{E}} E必须按照1到n的顺序依次将这些原料放入炼金锅。
但是,炼金锅的容量非常有限,它最多只能容纳w个原料。
所幸的是,每放入一个原料之前,小 E {\mathrm{E}} E可以从中取出一些原料,数量不能超过s个。
放入第 i {i} i种原料时所增加的耐久度为:锅内的原料总数 ∗ a i {*a_i} ∗ai,则宝剑的耐久度为所有原料的耐久度之和.
小 E {\mathrm{E}} E当然想让他的宝剑的耐久度尽可能得大,这样他就可以带着它进行更多的战斗,请求出耐久度的最大值。
注:这里的“放入第i种原料时锅内的原料总数包括正在放入锅中的原料,详细信息请见样例。
第一行,三个整数 n,w,s.
第二行,n个整数 a 1 , a 2 , … , a n {a_1,a_2,\dots,a_n} a1,a2,…,an.
一行一个整数,表示耐久度的最大值。
输入输出样例
输入 #1
5 3 3
1 3 2 4 5
输出 #1
40
输入 #2
5 3 3
1 -3 -2 4 5
输出 #2
21
输入 #3
7 4 2
-5 3 -1 -4 7 -6 5
输出 #3
17
输入 #4
5 3 1
-1 -3 -2 -4 -5
输出 #4
-15
说明/提示
对于100%的数据,有 1 ≤ s ≤ w ≤ n ≤ 5500 , 0 ≤ ∣ a i ∣ ≤ 1 0 9 {1 \leq s \leq w \leq n \leq 5500,0\leq |a_i| \leq 10^9} 1≤s≤w≤n≤5500,0≤∣ai∣≤109(时限 500 m s {500ms} 500ms)
对于所有测试点,空间限制 256 M B {256\mathrm{MB}} 256MB.
此题对于DP老手来说非常简单,但对于我这种DP蒟蒻却是男难上加难(连DP状态转移方程都不会推 )!下面说说思路.
我们可以先通过DP的解题套路来帮助思考:
第一:找状态,本题首先想到的便是用二维状态暴力DP解题.根据题意,我们很容易得知用 f [ i ] [ j ] {f[i][j]} f[i][j]来表示在第 i {i} i个原料放入后锅中还有 j {j} j个原料时的耐久度的最大值.
第二:基本处理,因为这道题的 a i {a_i} ai是可以为负数的,要求最大值就必须将所有状态量的初始值都赋为负无穷,又由于DP需要一个确定的原始状态量,所以我们得再把 f [ 0 ] [ 0 ] {f[0][0]} f[0][0]的值赋为0(显然 ).最后我们要求的答案 a n s {ans} ans便是 m a x ( f [ n ] [ j ] ) ( 1 = < j < = m ) {max(f[n][j])(1=
第三:求DP转移方程.这是DP的核心,也是我认为DP中最难的地方,但你只要样例够多或者有个暴力 DFS代码,慢慢试就行了,也不会用太长时间就试出来了.最后得出DP转移方程为:
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ k ] + j ∗ a [ i ] ) ( j − 1 < = k < = m i n ( m , j + s − 1 ) ) {f[i][j]=max(f[i][j],f[i-1][k]+j*a[i])(j-1<=k<=min(m,j+s-1))} f[i][j]=max(f[i][j],f[i−1][k]+j∗a[i])(j−1<=k<=min(m,j+s−1))
暴力全算出来即可,然后就可以开开心心交代码了!
#include
#define N 6000
#define ll long long
using namespace std;
ll n,m,s;
ll a[N],f[N][N],ans=-1e17+7;
int main()
{
scanf("%lld%lld%lld",&n,&m,&s);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
f[i][j]=-1e17+7;
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=m;j>0;j--)
{
for(int k=min(m,s+j-1);k>=j-1;k--)
f[i][j]=max(f[i][j],f[i-1][k]+j*a[i]);
}
for(int i=0;i<=m;i++)
ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0;
}
由于该算法的复杂度是 O ( m ∗ n 2 ) {O(m*n^2)} O(m∗n2),所以肯定会T.这时我就怒了:我TM 花两个小时来搞这一个大佬眼中1分钟就能切的简单DP题居然还被卡住了?!! 言归正传,遇到困难,我们不能放弃,要微笑着面对它! 在寻找一切尽可能优化的方式之后,我发现在:
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ k ] + j ∗ a [ i ] ) ( j − 1 < = k < = m i n ( m , j + s − 1 ) ) {f[i][j]=max(f[i][j],f[i-1][k]+j*a[i])(j-1<=k<=min(m,j+s-1))} f[i][j]=max(f[i][j],f[i−1][k]+j∗a[i])(j−1<=k<=min(m,j+s−1))
中, j ∗ a [ i ] {j*a[i]} j∗a[i]其实是个定值,所以我们就可以把它简化为:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ k ] ) + j ∗ a [ i ] ( j − 1 < = k < = m i n ( m , j + s − 1 ) ) {f[i][j]=max(f[i-1][k])+j*a[i](j-1<=k<=min(m,j+s-1))} f[i][j]=max(f[i−1][k])+j∗a[i](j−1<=k<=min(m,j+s−1))
然后我们就可以用熟悉的 数据结构单调队列来优化啦.
这使得复杂降为 O ( n ∗ m ) {O(n*m)} O(n∗m)稳过!
#include
#define N 6000
#define int long long//很保险的方式,不过主函数要改为signed main().
using namespace std;
int n,m,s;
int a[N],f[N][N],q[N],poi[N],ans=-1e17+7;
//q[]存储f[][]的值,poi[]存储f[][]的第二维.
signed main()
{
scanf("%lld%lld%lld",&n,&m,&s);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
f[i][j]=-1e17+7;
f[0][0]=0;
for(int i=1;i<=n;i++)
{
int l=1,r=1;
q[l]=f[i-1][m];
poi[l]=m;
for(int j=m;j>0;j--)//单调队列线性优化
{
while(l<=r&&poi[l]>(s+j-1))l++;
while(l<=r&&q[r]<f[i-1][j-1])r++;
poi[++r]=j-1;
q[r]=f[i-1][j-1];
f[i][j]=q[l]+a[i]*j;
}
}
for(int i=0;i<=m;i++)
ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0;
}
希望这篇博客对各位巨佬 DP新手们有所帮助。