[笔记]背包 DP 做题记录

P2340 [USACO03FALL] Cow Exhibition G

\(f[i][j][k]\) 表示前 \(i\) 个元素,智商之和为 \(j\),情商之和为 \(k\)。这样表示显然很冗余,因为答案已经包含在状态里面了。

一个常见的技巧是将键挪到值处,即用 \(f[i][j]\) 来表示前 \(i\) 个元素智商之和为 \(j\),情商之和的最大值。

转移和背包类似:

\[f[i][j]\gets \min(f[i-1][j],f[i-1][j-x[i]]+y[i]) \]

需要注意两点:

  • 由于第二维可能出现负数,所以存储时需要整体平移一个值域。
  • 必须滚动数组,否则会 MLE。滚动数组时 \(j\) 的枚举方向需要根据 \(x[i]\) 决定。\(x[i]\) 为正则倒序枚举,\(x[i]\) 为负则正序枚举。

总时间 \(O(nV)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=405,V=400000;
struct Nd{int x,y;}a[N];
int n,f[2*V+5],ans;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y;
	memset(f,-0x3f,sizeof f);
	f[V]=0;
	for(int i=1;i<=n;i++){//a[i].x>0则倒序 否则正序 
		if(a[i].x>0){
			for(int j=V+V;j>=a[i].x;j--){
				f[j]=max(f[j],f[j-a[i].x]+a[i].y);
			}
		}else{
			for(int j=0;j<=V+V+a[i].x;j++){
				f[j]=max(f[j],f[j-a[i].x]+a[i].y);
			}
		}
	}
	for(int i=0;i<=V;i++){
		if(f[i+V]>=0) ans=max(ans,i+f[i+V]);
	}
	cout<<ans<<"\n";
	return 0;
}

P4141 消失之物

若对每个删除的元素都暴力跑一次背包,将是 \(O(n^2m)\) 的,无法接受。

但是考虑这道题统计时是累加,所以是可以撤销掉某个元素的贡献的。

只需要在最终 \(f\) 数组的基础上,把枚举到第 \(i\) 个元素时做的事情倒着做回去,这样就把 \(i\) 的贡献撤销掉了。

另一种理解方式是,枚举 \(i\) 的顺序对答案无影响,所以考虑撤销第 \(i\) 个元素的贡献时,我们可以看做它是最后一个被枚举到的,那么最后一轮做什么,我们就倒着做回去。

总时间 \(O(nm)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2005,M=2005;
int n,m,w[N],f[M],g[M];
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	f[0]=g[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--) f[j]=(f[j]+f[j-w[i]])%10;
	}
	for(int i=1;i<=n;i++){
		memcpy(g,f,sizeof f);
		for(int j=w[i];j<=m;j++) g[j]=(g[j]-g[j-w[i]]+10)%10;
		for(int j=1;j<=m;j++) cout<<g[j];
		cout<<"\n";
	}
	return 0;
}

P4158 [SCOI2009] 粉刷匠

对于第 \(i\) 行,我们可以为其分配 \(j\) 次染色操作,能获得一定的收益 \(w(i,j)\)(即刷对的位置个数)。可以发现每行就是一个泛化物品。

考虑对于第 \(i\) 行如何求出 \(g[i][j]\),即前 \(i\) 个元素染 \(j\) 次能获得的最大收益。那么 \(w(i,j)=g[m][j]\)

显然有 \(g\) 的转移:

\[g[i][j]\gets g[k][j-1]+\min(cnt_0[k+1,i],cnt_1[k+1,i]) \]

求出每个泛化物品后,可以在 \(O(tm)\) 的复杂度内完成合并。总时间 \(O(tnm)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=55,T=2505;
int n,m,t,g[N][N],c[N],w[N][N],f[T];
string s[N];
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>t;
	for(int x=1;x<=n;x++){
		cin>>s[x],s[x]=' '+s[x];
		for(int i=1;i<=m;i++) c[i]=c[i-1]+s[x][i]-'0';
		for(int i=1;i<=m;i++){
			for(int j=1;j<=i;j++){
				g[i][j]=0;
				for(int k=0;k<i;k++){
					g[i][j]=max(g[i][j],g[k][j-1]+max(c[i]-c[k],i-k-(c[i]-c[k])));
				}
			}
		}
		for(int i=1;i<=m;i++) w[x][i]=g[m][i];
	}
	int s=0;
	for(int i=1;i<=n;i++){
		s+=m;
		for(int j=min(t,s);j;j--){
			for(int k=min(j,m);~k;k--){
				f[j]=max(f[j],f[j-k]+w[i][k]);
			}
		}
	}
	cout<<f[t]<<"\n";
	return 0;
}

P2851 [USACO06DEC] The Fewest Coins G

考虑枚举找零个数 \(x\)。另外维护:

  • \(f[i]\) 为总额恰好为 \(i\) 的最小金币数,多重背包。
  • \(g[i]\) 为总额恰好为 \(i\) 的最小金币数,完全背包。

那么答案就是:

\[\min_x f[x]+g[x-T] \]

其中左边是 FJ 付钱,右边是店主找零。

注意多重背包需要二进制 / 单调队列优化。

最后一个问题是 \(x\) 的枚举范围,下界显然是 \(T\),一个很松的上界是 \(\sum C[i]V[i]\),但这太大了。

有结论只需要枚举到 \(V_{\max}^2\) 即可,证明先不补了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=102,M=10002,V=122;
int n,m,mx,v[N],c[N],f[M+V*V],g[V*V],ans=1e9;//f:多重  g:完全 
signed main(){
//	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i],mx=max(mx,v[i]);
	for(int i=1;i<=n;i++) cin>>c[i];
	mx=mx*mx;
	memset(f,0x3f,sizeof f);
	memset(g,0x3f,sizeof g);
	f[0]=g[0]=0;
	//完全背包 
	for(int i=1;i<=n;i++)
		for(int j=v[i];j<=mx;j++)
			g[j]=min(g[j],g[j-v[i]]+1);
	//多重背包 
	for(int x=1;x<=n;x++){
		for(int i=1;i<=c[x];i<<=1){
			for(int j=m+mx;j>=i*v[x];j--)
				f[j]=min(f[j],f[j-i*v[x]]+i);
			c[x]-=i;
		}
		if(c[x]) for(int j=m+mx;j>=c[x]*v[x];j--)
			f[j]=min(f[j],f[j-c[x]*v[x]]+c[x]);
	}
	//枚举找零数
	for(int i=m;i<=m+mx;i++){
		ans=min(ans,f[i]+g[i-m]);
	}
	cout<<(ans==1e9?-1:ans)<<"\n";
	return 0;
}
posted @ 2025-11-18 16:25  Sinktank  阅读(128)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.