数字组合 自然数的拆分

0-1背包问题:

数字组合

/*
数字组合问题:
从N个数中选择一些数进行求和,求多少种选法可以使得到的和是m,输入数据包含第一行N和M,第二行N个数
其中N最大取100,M最大取10000,每个数最大取1000,
答案保证在int范围内
0-1背包问题:
使用f[i][j]表示从前i个数中选,选出若干个数,得到的和是j的所有可能的选法的集合
F[i][j]表示的属性是个数count
转换关系,考虑最后一个不同点,第i个数是选还是不选,有f[i-1][j]和f[i-1][j-wi];

初始化:f[i][0]=1,
For i  in [1,2,3,…,n]:
	For j in [1,2,3,…,m]
		F[i][j]=f[i-1][j]+f[i-1][j-w[i]];
		
边界条件:f[i][0]=1,不选这一种方案;
探讨f[i][0]为什么是1,根据定义,f[i][0]表示从前i个数中选择数,
可以凑到和为0的所有集合,这样的集合个数是1,集合里面的元素是空集,
表示一个数都不选,为什么f[i][0]不是0,即,从前i个数里面选若干个数,
无论怎么选都不会使和为0,因为任意选一个数都会使和不为0,
但是,不为0是在至少选了一个数的限制下有的,
所以,上述想法还是没有考虑到一个数都不选这种情况。
另外,使用枚举来思考,对于前i个数,每个数都存在两种状态,
选或者不选,完全存在这i个数一个都不选这种情况,

F[1][w1]=1;为什么f[1][w1]是1,从前1个数里面选若干个数,
只有两种情况,不选这个数和选这个数,只有选了w1这个数
这种情况才能使和是w1,情况数是1,所以是f[1][w1]=1;
在f[1][w1]的上面讨论也可以看出,还是存在不选w1这个数这种情况,
也就相当于一个数都不选这种情况,所以f[1][0]=1同样是成立的。

从第二个方案开始枚举
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const int N=110,M=10010;
int n,m;
int f[N][M];
int a[N];

//
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i<= n;i++)scanf("%d",&a[i]),f[i][0]=1;
	//f[0][0]=1;//这个数据是造出来的//为什么造这个数据
	//f[i][j]=f[i-1][j]+f[i-1][j-wi] 
	//f[1][w1]=1是显然的,根据定义一定是1
	//f[1][w1]=f[0][w1]+f[0][0];//f[1][0]根据上述是1 , 
	//可以初始化第一层,第2.。。n层递推 
	f[1][a[1]]=1;//
	for(int j = 1;j <= m;j++)cout<<f[1][j]<<" ";
	cout<<endl;
	for(int i = 2;i <= n;i++){
		for(int j = m;j-a[i]>=0;j--)
			f[i][j]=f[i-1][j]+f[i-1][j-a[i]],cout<<f[i][j]<<" ";
		cout<<endl;
	}
		
	cout<<f[n][m]<<endl;
	return 0;
} 

由于

1.第i层只和上一层有关系,使用滚动数组求解,

2.f[i][j]=f[i-1][j]+f[j-wi],更新第i层的j需要用到上一层的j和j-wi,从尾部遍历,即可,具体,使用f[j]=f[j]+f[j-wi],j从m开始递减,到wi结束,因为j-wi要大于等于0,取前i个数中的若干个数,和为0的方案有1中,就是一个数都不选这种状态(对应二进制压缩每个二进制位都是0)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const int N=110,M=10010;
int n,m;
int f[M];
int a[N];

//
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i<= n;i++)scanf("%d",&a[i]);
	f[0]=1;//
	for(int i = 1;i <= n;i++){
		for(int j = m;j-a[i]>=0;j--)
			f[j]=f[j]+f[j-a[i]];
	}	
	cout<<f[m]<<endl;
	return 0;
} 

0-1完全背包问题:

自然数的拆分:

/*
自然数的拆分:
给定一个自然数N,要求把N拆分成若干个正整数相加的形式,参与加法运算的数可以重复,
拆分的方案不考虑顺序,至少拆分成2个数的和

求拆分的方案数mod 2147483648的结果

输入一个自然数N
输出一个整数,表示结果
数据范围是N在1到4000
输入样例
7
输出样例:
14
解释:
如何转化为背包问题:
物品:自然数1,2,3,4,。。。,N
背包:背包容量是N
物品可以重复:
F[I][j]表示前i个数中选若干个数,得到的和是j的所有可能的方案的集合
属性·:集合的个数
转移方程:最后一个不同点,第i个数选不选,
第i个数不选,f[i-1][j];
第i个数选择,选1个,f[i-1][j-wi];选2个,f[i-1][j-2*wi],…,选k个,f[i][j-k*wi]
枚举i和j,需要n2;再加上枚举k,复杂度n2log2n
状态转移方程,f[i][j]=
f[i-1][j]+f[i-1][j-wi]+ f[i-1][j-2*wi]+ f[i-1][j-3*wi]+…+ f[i-1][j-k*wi]
F[i][j-wi]=
f[i-1][j-wi]    + f[i-1][j-2*wi]+ f[i-1][j-3*wi]+…+ f[i-1][j-k*wi];

再看这个公式:
f[i][j]=
f[i-1][j]+f[i-1][j-wi]+ f[i-1][j-2*wi]+ f[i-1][j-3*wi]+…+ f[i-1][j-k*wi]
前i个数中取若干个数和为j的所有方案数:
分类讨论:第i个数取k次(讨论第i次的取值情况),
前i-1个数中选择若干个数和为j-k*wi;
前i个数中取若干个数和为j的所有方案数等于
前i-1个数中选择若干个数和为j-k*wi的所有方案数,k=0,1,2,3,…

前i个数中取若干个数和为j-wi的所有方案数等于
前i-1个数中选择若干个数和为j-k*wi的所有方案数,k=1,2,3,…

前i个数中取若干个数和为j-2*wi的所有方案数
等于前i-1个数中选择若干个数和为j-k*wi的所有方案数,k=2,3,4,…

因此
F[i][j]=f[i-1][j]+f[i][j-wi];

F[i][j]只和上一层有关以及当前层的前面位置j-wi处有关

需要当前层的位置j-wi处的值更新j处的值,所以对于每一层进行从小到大遍历;
F[0]=1;
F[N]
For(int I = 1;I <= n;i++)
	For(int j = 1; j <= n;j++)
If j-w[i]>=0:
F[j]+=f[j-w[i]];
Cout<<f[N]-1;


*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const int N=4010;
int f[N];
int n;
int main(){
	cin>>n;
	f[0]=1;
	for(int i = 1 ;i <= n;i++)
		for(int j = 1; j<=n;j++)
			if(j-i>=0)
				f[j]+=f[j-i];
	cout<<f[n]-1<<endl;
	return 0;
}

少一点判断,j从i开始

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const unsigned int N=4010,mod=2147483648;
unsigned int f[N];
int n;
int main(){
	cin>>n;
	f[0]=1;
	for(int i = 1 ;i <= n;i++)
		for(int j = i; j<=n;j++)
			{
			    f[j]+=f[j-i];
			    f[j]=f[j]%mod;
			}
				
	cout<<(f[n]-1)%mod<<endl;
	return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值