有(n)个栈,每个栈中有若干个带非负价值的物品。每次可以取出栈顶的物品,要求取出恰好(k)个物品,最大化物品的价值和。
价值满足:(a_{i,j}le a_{i,j+1})。
(n,kle 3000)
(sum |a_i|le 10^6)
结论:最优解至多有一个选了一些但没有选完的栈。
证明:考虑有两个这样的栈,记最后一个为(a_{1,last_1})和(a_{2,last_2}),如果(a_{1,last_1}le a_{2,last_2}),因为(a_{2,last_2+1}ge a_{2,last_2}),不选(a_{1,last_1})选(a_{2,last_2+1})更优。
考虑暴力:枚举哪个栈选了但没有被选完,然后对另外(n-1)个栈做01背包。时间复杂度(O(n^3))。
可以优化,分治,记dp数组表示区间外的栈的补集。进入左儿子前,备份一下,然后枚举右区间的每个数,分别(O(k))地更新一下dp数组。
由于每个栈对dp数组的贡献次数为(O(lg n)),所以时间(O(n^2lg n))
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 3005
#define ll long long
int n,K;
vector<int> a[N];
ll sum[N];
//void merge(ll c[],ll a[],ll b[]){
// memset(c,0,sizeof(ll)*(K+1));
// for (int i=K;i>=0;--i)
// for (int j=i;j>=0;--j)
// c[i]=max(c[i],a[j]+b[i-j]);
//}
void upd(ll f[],int w,ll v){
for (int i=K;i>=w;--i)
f[i]=max(f[i],f[i-w]+v);
}
ll f[15][N];
ll ans;
void divide(int k,int l,int r){
if (l==r){
ll s=0;
for (int i=0;i<a[l].size();++i){
ans=max(ans,f[k][K-i]+s);
s+=a[l][i];
}
ans=max(ans,f[k][K-a[l].size()]+s);
return;
}
int mid=l+r>>1;
memcpy(f[k+1],f[k],sizeof(ll)*(K+1));
for (int i=mid+1;i<=r;++i)
upd(f[k+1],a[i].size(),sum[i]);
divide(k+1,l,mid);
memcpy(f[k+1],f[k],sizeof(ll)*(K+1));
for (int i=l;i<=mid;++i)
upd(f[k+1],a[i].size(),sum[i]);
divide(k+1,mid+1,r);
}
int main(){
// freopen("in.txt","r",stdin);
scanf("%d%d",&n,&K);
for (int i=1;i<=n;++i){
int t;
scanf("%d",&t);
for (int j=0;j<t;++j){
int x;
scanf("%d",&x);
a[i].push_back(x);
}
while (a[i].size()>K)
a[i].pop_back();
for (int j=0;j<a[i].size();++j)
sum[i]+=a[i][j];
}
divide(0,1,n);
printf("%lld
",ans);
return 0;
}