题面传送门
看到这道题,暴力应该是很好打的,爆搜出放一件物品前要拿走多少,然后就记忆化出 d p dp dp了:设 f i , j f_{i,j} fi,j为放到第 i i i个物品,锅内加上当前还有 j j j件物品的最大总和,那么状态转移方程应该是 f i , j = m a x ( f i , k + a i ∗ j ) f_{i,j}=max(f_{i,k}+a_i*j) fi,j=max(fi,k+ai∗j),其中 k k k表示没有拿走 < s <s件物品时锅内剩余的物品数,那么根据定义就可以确定 k k k的范围: j − 1 ≤ k ≤ m i n ( m , s + j − 1 ) j-1\leq k\leq min(m,s+j-1) j−1≤k≤min(m,s+j−1),其中 j − 1 j-1 j−1表示什么都不拿,为什么要 − 1 -1 −1呢,因为我们还要放进去一件物品;而 s + j − 1 s+j-1 s+j−1表示拿出了最大上限 s s s件物品。在这道题目中从已有状态推过去状态会好推一点,如果从已有状态推未来状态就要考虑越界就会很烦。这样时间复杂度 O ( n m s ) O(nms) O(nms)
代码实现:
#include
#include
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
long long n,m,s,f[5539][5539],ans=-1e15,tot,pus,a[5539],now,last;
int main(){
register int i,j,k;
scanf("%lld%lld%lld",&n,&m,&s);
for(i=0;i<=n;i++){
for(j=0;j<=m;j++) f[i][j]=-1e15;
}
for(i=1;i<=n;i++)scanf("%lld",&a[i]);
f[0][0]=0;
for(i=1;i<=n;i++){
for(j=1;j<=min(m,i);j++){
for(k=j-1;k<=min(m,s+j-1);k++) f[i][j]=max(f[i-1][k]+j*a[i],f[i][j]);
}
}
for(i=0;i<=m;i++)ans=max(ans,f[n][i]);
printf("%lld\n",ans);
}
居然有 85 85 85分,要在 P J PJ PJ考场上我就直接开下一题了,毕竟就 15 15 15分的事( z y q zyq zyq:我少 15 15 15分会怎么样你知道吗?)
然后我们要考虑优化
观察一下 d p dp dp方程式: f i , j = m a x ( f i , k + a i ∗ j ) f_{i,j}=max(f_{i,k}+a_i*j) fi,j=max(fi,k+ai∗j),我们可以乱搞一下,变成: f i , j = m a x ( f i , k ) + a i ∗ j f_{i,j}=max(f_{i,k})+a_i*j fi,j=max(fi,k)+ai∗j,因为 a i ∗ j a_i*j ai∗j是固定的,然后由于 k k k的界限随 j j j而变化,我们想到求解状态的一重循环这是一个区间取最值的 s b sb sb操作,可以用 s t st st表/线段树/单调队列来求解,由于前两个都带 l o g log log,就用单调队列吧。
我们来分析一下单调队列的适用性,当推到 f i , j f_{i,j} fi,j时, f i − 1 , j f_{i-1,j} fi−1,j入队,但同时这个状态所有待转移的状态都要已经入队过了(注意这个”过”字,单调队列不要求一定要留在队列里),而 k k k的取值范围是 j − 1 ≤ k ≤ m i n ( m , s + j − 1 ) j-1\leq k\leq min(m,s+j-1) j−1≤k≤min(m,s+j−1),拆成 m a x ( f i − 1 , j − 1 , m a x ( f i − 1 , k ) ) + a i ∗ j max(f_{i-1,j-1},max(f_{i-1,k}))+a_i*j max(fi−1,j−1,max(fi−1,k))+ai∗j, j ≤ k ≤ m i n ( m , s + j − 1 ) j≤k≤min(m,s+j-1) j≤k≤min(m,s+j−1),这样反转一下j的循环枚举顺序就可以保证所有转移状态都入队了。
代码实现:
#include
#include
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
long long n,m,s,f[5539][5539],ans=-1e15,tot,pus,a[5539],now,last,q[5539],head,tail;
int main() {
register long long i,j,k;
scanf("%lld%lld%lld",&n,&m,&s);
for(i=0; i<=n; i++) {
for(j=0; j<=m; j++) f[i][j]=-1e15;
}
for(i=1; i<=n; i++)scanf("%lld",&a[i]);
f[0][0]=0;
for(i=1; i<=n; i++) {
head=tail=0;
for(j=min(m,i); j>=1; j--) {
//for(k=j-1;k<=min(m,s+j-1);k++) f[i][j]=max(f[i-1][k],f[i][j]);
while(q[head+1]>s+j-1&&head!=tail) head++;
while(f[i-1][q[tail]]<f[i-1][j]&&head!=tail) tail--;
q[++tail]=j;
f[i][j]=max(f[i-1][q[head+1]],f[i-1][j-1]);
f[i][j]+=j*a[i];
}
}
for(i=0; i<=m; i++)ans=max(ans,f[n][i]);
printf("%lld\n",ans);
}
最后不得不吐槽一句:单调队列常数好大啊!