Codeforces 1442D - Sum(找性质+分治+背包)

Codeforces 题面传送门 & 洛谷题面传送门

智商掉线/ll

本来以为是个奇怪的反悔贪心,然后便一直往反悔贪心的方向想就没想出来,看了题解才发现是个 nb 结论题。

Conclusion. 在最优方案中,至多只有一个数组只有部分被选,其余数组要么全选要么全都不选。

证明:考虑调整。假设存在两个数组 \(x,y\) 分别选了前 \(p,q\) 个元素,这里不妨假设 \(a_{x,p+1}\ge a_{y,q+1}\),那么考虑从 \(y\) 数组中拎 \(l=\min(len_x-p,q)\) 个元素到 \(x\) 中,那么答案的增量

\[\Delta=\sum\limits_{i=1}^la_{x,p+i}-\sum\limits_{i=1}^la_{y,q-i+1} \]

由于 \(a_x,a_y\) 均为单调数组并且 \(a_{x,p+1}\ge a_{y,q+1}\),故 \(a_{x,p+i}\ge a_{y,q-i+1}\),因此 \(\Delta\ge 0\) 必然成立。如此调整下去即可得证。

有了这个性质之后如何求解答案呢?一个很自然的想法是对前后缀分别跑一遍背包,然后枚举那个选了一部分的数组及其选择的长度,然后用背包合并的技巧合并。但这样复杂度是三方的,如果将 \(n,k\) 视作同阶。一脸过不去的样子。考虑优化,注意到对于背包而言,其合并的复杂度可能很高,达到平方,但插入的复杂度并不算高,因此考虑分治,具体来说当分治一个区间时,我们记 \(mid=\lfloor\dfrac{l+r}{2}\rfloor\),然后将 \([l,mid]\) 中数组插入背包,分治 \([mid+1,r]\),然后将背包还原成原来的样子,插入 \([mid+1,r]\) 中的数组,分治 \([l,mid]\),再复原即可实现 \(nk\log n\) 的复杂度。

感觉我博客里有 114514191981019260817998244353 个“找性质”啊,为啥我这类找性质的题目还是做不出来呢?zibile,zabanna/dk

const int MAXN=3000;
int n,k,len[MAXN+5];
ll sum[MAXN+5],res=0;
vector<int> a[MAXN+5];
struct knap{
	ll dp[MAXN+5];
	void clear(){memset(dp,0,sizeof(dp));}
	knap(){clear();}
	void insert(int v,ll w){for(int i=k;i>=v;i--) chkmax(dp[i],dp[i-v]+w);}
};
knap cur;
void solve(int l,int r){
	if(l==r){
		ll s=0;
		for(int i=0;i<=a[l].size();i++){
			if(k>=i) chkmax(res,s+cur.dp[k-i]);
			if(i!=a[l].size()) s+=a[l][i];
		} return;
	} int mid=l+r>>1;knap tmp=cur;
	for(int i=l;i<=mid;i++) cur.insert(len[i],sum[i]);
	solve(mid+1,r);cur=tmp;
	for(int i=mid+1;i<=r;i++) cur.insert(len[i],sum[i]);
	solve(l,mid);cur=tmp;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%d",&len[i]);
		for(int j=1,x;j<=len[i];j++){
			scanf("%d",&x);sum[i]+=x;
			a[i].pb(x);
		}
	} solve(1,n);
	printf("%lld\n",res);
	return 0;
}
posted @ 2021-09-27 19:42  tzc_wk  阅读(71)  评论(0编辑  收藏  举报