(前缀和优化多重背包)[HAOI2008] 硬币购物

一眼看过去,多重背包题

但是,s和d的数据范围是1e5,按照多重背包的O(\(s\sum d\))肯定会炸

这时候,就有一个常用的前缀和优化多重背包可以使多重背包复杂度变为O(\(sn\)) (n为物品数

具体来说,我们可以认为完全背包是特殊情况下的多重背包,考虑完全背包的情况,完全背包可以直接由同一层的前一个点转移过来,对于每一种物品可以在O(s)的时间复杂度下完成,这个转移的过程类似于求前缀和的过程,用前缀和维护同一层的dp值

现在,我们可以将同一个物品全部放到同一层考虑转移,考虑特殊情况,当物品的体积都为1时,对于完全背包,就相当于对上一层的dp值求前缀和,对于多重背包,就相当于求上一层一段区间内的dp值之和,也可以用前缀和优化,而当物体体积不为1时,可以将上一层的dp值按照其下标取余当前层物品的体积的余数分组,分别求出每一组的前缀和,在转移时就相当于在分组后的dp序列中求区间和,用前缀和优化

这样,我们就在O(ns)的复杂度下求出了多重背包的方案数,对于这道题,物品数为4可以直接视为常数,最终复杂度就是O(ns)的复杂度下解决了这道题(这里n指多测组数

AC代码

using namespace std;
#define ll long long
#define MAXN 100010
inline ll read(){
	ll x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}
ll c[5],dp[5][MAXN],pre[5][MAXN];
ll d[5];
ll n,s;
int main(){
	for(int i=1;i<=4;i++)c[i]=read();
	n=read();
	for(int i=0;i*c[1]<=100000;i++)pre[0][i*c[1]]=1;
	while(n--){
		for(int i=1;i<=4;i++)d[i]=read();
		s=read();
		for(int i=1;i<=4;i++){
			for(int j=0;j<=s;j++){
				dp[i][j]=pre[i-1][j]-(j-(d[i]+1)*c[i]<0?0:pre[i-1][j-(d[i]+1)*c[i]]);
				pre[i][j]=dp[i][j]+(j-c[i+1]<0?0:pre[i][j-c[i+1]]);
			}
		}
		cout<<dp[4][s]<<endl;
	}
	return 0;
}
posted @ 2024-11-24 16:40  flyfreemrn  阅读(46)  评论(0)    收藏  举报