0710-动态规划-HDU5800

knapsack=背包(英语matters a lot)是的,没错,这道题就叫Knapsack(可蠢蠢的我即使知道是背包也无从下手,所以说思维matters most)

knapsack(和HDU 有点点差别,不过问题不大)

 

题目描述

有 n 个物品,第 i 个物品的重量为 ai 。
设 f(i,j,k,l,m) 为满足以下约束的物品集合数量:
1.集合中所有物品的重量和恰好为 m 。
2.集合包含物品 i 和物品 j 。
3.集合不包含物品 k 和物品 l 。
给出一个正整数 s ,求:

    

答案对 109+7 取模。

输入格式

第一行,两个正整数 n,s 。
第二行,n 个正整数 ai,描述每个物品的重量。

输出格式

输出一行,一个整数表示答案对 109+7 取模后的结果。

样例数据 1

输入  

4 4 
1 2 3 4

输出

8

备注

【数据规模与约定】
对于 30% 的数据,n,s≤10
对于另 20% 的数据,ai=1
对于 80% 的数据,n,s≤100
对于 100% 的数据,n,s≤1000

背包计数

其实很多的题目都有类似的特点,答案并不好求,但我们可以通过求出包含答案的一个东西,然后减去多余的部分(或是从中挑选我们需要的),就可以得到答案了,T3大概也是这样的。这也是正难求反思路的体现

下面上一个好写好理解的、但压线过(说不定哪一次就卡常数过不去了)的代码

用dp[i][j][s1][s2] (代码中是f来表示的) 来表示选前i个数,达到j的重量,且必选的数有s1个、必不选的数有s2个,的数量

 那么转移的时候可以考虑,第i个物品是可选可 可不选的
 dp[i][j][s1][s2]+=dp[i-1][j][s1][s2]+dp[i-1][j-a[i]][s1][s2]
 或者第i个物品必选,或者必不选

  dp[i][j][s1][s2]+=dp[i-1][j-a[i]][s1-1][s2]+dp[i-1][j][s1][s2-1](摘自感谢这位大佬

(实现的话看代码吧,努力详细)

然后其实用不用滚动数组都可以,空间不会爆

#include<cstdio>//这个代码很玄。。最后一组数据刚好2000ms,压线过
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
int n,s,a[2009];
long long f[3][2009][4][4];
const int mod=1e9+7;
int main(){
	scanf("%d%d",&n,&s);
	int i,j,k,s1,s2;
	for(i=1;i<=n;++i){
		scanf("%d",&a[i]);
	}
	bool sign=0;
	f[0][0][0][0]=1;//对于选0个数,使其重量达到0,并且s1、s2都为0,就是什么都不做嘛,那什么都不做就只有1种可能啦
	for(i=1;i<=n;++i){
		sign=!sign;//滚动数组的体现
		for(j=s;j>=0;--j)//必须逆序哦,否则会重复放
		for(s1=0;s1<=2;++s1)//0表示没有一个数必选,1表示一个必选,2表示两个都必选
			for(s2=0;s2<=2;++s2){
				f[sign][j][s1][s2]=f[!sign][j][s1][s2]%mod;这个地方千万不能写成+=,因为用的是滚动数组,
                                                                           ///每次都是覆盖上去,如果是+=的话上次的结果会一直跟到最后
				if(j-a[i]>=0){
					f[sign][j][s1][s2]+=f[!sign][j-a[i]][s1][s2];
					f[sign][j][s1][s2]%=mod;
					if(s1>=1) f[sign][j][s1][s2]+=f[!sign][j-a[i]][s1-1][s2],f[sign][j][s1][s2]%=mod;
				}	
				if(s2>=1) f[sign][j][s1][s2]+=f[!sign][j][s1][s2-1],f[sign][j][s1][s2]=f[sign][j][s1][s2]%mod;
			}
	}
	long long ans=0;
	for(i=1;i<=s;++i)
		ans=(ans+f[sign][i][2][2])%mod;
	ans=(ans*2)%mod*2%mod;//因为在s1、s2用0、1、2表示时,是默认了i<j,k<l所以要最终答案×4
	printf("%I64d",ans);
	return 0;
}//这个代码很玄。。最后一组数据刚好2000ms,压线过
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
int n,s,a[2009];
long long f[3][2009][4][4];
const int mod=1e9+7;
int main(){
	scanf("%d%d",&n,&s);
	int i,j,k,s1,s2;
	for(i=1;i<=n;++i){
		scanf("%d",&a[i]);
	}
	bool sign=0;
	f[0][0][0][0]=1;//对于选0个数,使其重量达到0,并且s1、s2都为0,就是什么都不做嘛,那什么都不做就只有1种可能啦
	for(i=1;i<=n;++i){
		sign=!sign;//滚动数组的体现
		for(j=s;j>=0;--j)//必须逆序哦,否则会重复放
		for(s1=0;s1<=2;++s1)//0表示没有一个数必选,1表示一个必选,2表示两个都必选
			for(s2=0;s2<=2;++s2){
				f[sign][j][s1][s2]=f[!sign][j][s1][s2]%mod;这个地方千万不能写成+=,因为用的是滚动数组,
                                                                           ///每次都是覆盖上去,如果是+=的话上次的结果会一直跟到最后
				if(j-a[i]>=0){
					f[sign][j][s1][s2]+=f[!sign][j-a[i]][s1][s2];
					f[sign][j][s1][s2]%=mod;
					if(s1>=1) f[sign][j][s1][s2]+=f[!sign][j-a[i]][s1-1][s2],f[sign][j][s1][s2]%=mod;
				}	
				if(s2>=1) f[sign][j][s1][s2]+=f[!sign][j][s1][s2-1],f[sign][j][s1][s2]=f[sign][j][s1][s2]%mod;
			}
	}
	long long ans=0;
	for(i=1;i<=s;++i)
		ans=(ans+f[sign][i][2][2])%mod;
	ans=(ans*2)%mod*2%mod;//因为在s1、s2用0、1、2表示时,是默认了i<j,k<l所以要最终答案×4
	printf("%I64d",ans);
	return 0;
}

下面再来一个,真正的正解(只要你搞明白了上面那个程序,这个就很好理解了,我就不赘述了)

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
int n,s,a;
long long f[2009][4][4],g[2009][4][4];
const int mod=1e9+7;
void add(long long &a,long long b){//这个地方的&非常重要,相当于让主函数里的g数组也更改了
	a+=b;
	if(a>=mod) a-=mod;
}
int main(){
	scanf("%d%d",&n,&s);
	int i,j,k,s1,s2;
	f[0][0][0]=1;
	g[0][0][0]=1;
	for(i=1;i<=n;++i){
		scanf("%d",&a);
		for(k=0;k<=s;++k)
			for(s1=0;s1<=2;++s1)
				for(s2=0;s2<=2;++s2)
				{
					if(f[k][s1][s2]){
						if(k+a<=s){
							add(g[k+a][s1][s2],f[k][s1][s2]);
							if(s1<2) add(g[k+a][s1+1][s2],f[k][s1][s2]);
						}
						if(s2<2) add(g[k][s1][s2+1],f[k][s1][s2]);
					}
				}
		memcpy(f,g,sizeof(f));
	}
	long long ans=0;
	for(i=1;i<=s;++i)
	{
		ans=(ans+4*f[i][2][2])%mod;
	}
	printf("%I64d",ans);
	return 0;
}//这个地方的&非常重要,相当于让主函数里的g数组也更改了
	a+=b;
	if(a>=mod) a-=mod;
}
int main(){
	scanf("%d%d",&n,&s);
	int i,j,k,s1,s2;
	f[0][0][0]=1;
	g[0][0][0]=1;
	for(i=1;i<=n;++i){
		scanf("%d",&a);
		for(k=0;k<=s;++k)
			for(s1=0;s1<=2;++s1)
				for(s2=0;s2<=2;++s2)
				{
					if(f[k][s1][s2]){
						if(k+a<=s){
							add(g[k+a][s1][s2],f[k][s1][s2]);
							if(s1<2) add(g[k+a][s1+1][s2],f[k][s1][s2]);
						}
						if(s2<2) add(g[k][s1][s2+1],f[k][s1][s2]);
					}
				}
		memcpy(f,g,sizeof(f));
	}
	long long ans=0;
	for(i=1;i<=s;++i)
	{
		ans=(ans+4*f[i][2][2])%mod;
	}
	printf("%I64d",ans);
	return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值