CF1442D Sum

\(n\)个栈,每个栈中有若干个带非负价值的物品。每次可以取出栈顶的物品,要求取出恰好\(k\)个物品,最大化物品的价值和。

价值满足:\(a_{i,j}\le a_{i,j+1}\)

\(n,k\le 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^2\lg 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\n",ans);
	return 0;
}
posted @ 2020-11-03 11:52  jz_597  阅读(173)  评论(0编辑  收藏  举报