CF1442D Sum

(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;
}