背包问题合集

一般背包问题中dp表示的都是题目中让求解的量

1.求1-n拆分成两个相等的子集合的个数
思路:典型的0-1背包问题
设dp[i][j]为1-i个数字当中可以组成和为j的个数,
有两个选择,选择i或者不选择i
选择i dp[i][j]=dp[i][j-i]
不选择i dp[i][j]=dp[i-1][j-i]
状态转移方程
所以dp[i][j]=dp[i][j-i]+dp[i-1][j-1]
由于只是i取决于上一个值所以可将状态转移方程转换成
dp[j]=dp[j-i]+dp[j-1]
初始状态
dp[0]=1

int main()
{
	int n;
	cin >> n;

	int sum = n * (n + 1) / 2;
	if (sum % 2!= 0)
	{
		cout << "0" << endl;
		return 0;
	}
	sum = sum / 2;
	
	for (int i = 1; i <= n; i++)
		dp[i] = 0;
	dp[0] = 1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = sum; j >= i; j--)
		{
			dp[j] += dp[j - i];
		}
	}

	cout << dp[sum]/2  << endl;

	return 0;
}

2.点菜问题
输入C和N,C表示的是报销价格,N表示的菜品个数
接下来的N行每行输入两个数,一个数是菜的价格,一个数是菜的评价分数
输出在报销价格范围内所点的菜的最大评价分数

分析:
dp[i][j]表示前i个菜中可以得到的价值最大数,j表示的钱的数量
在当前菜品中,可以选择菜品i 也可以不选i
选择i dp[i][j]=dp[i-1][j-p[i]]+v[i]
不选择i dp[i][j]=dp[i-1][j]
状态转移方程
dp[i][j]=max(dp[i-1][j-p[i]]+v[i],dp[i-1][j])
dp[j]=max(dp[j-p[j]]+v[i],dp[j])
初始条件
dp[…]=0

#include<cstdio>
#include<iostream>

using namespace std;

const int MAXN = 100;
struct food
{
	int price;
	int value;	
};
 
food arr[MAXN];
int dp[MAXN];
int c, n;

int main()
{
	cin >> c >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i].price >> arr[i].value;
	}
	for (int i = 0; i < c; i++)
		dp[i] = 0;

	for (int i = 0; i < n; i++)
	{
		for (int j = c; j >= arr[i].price; j--)
		{
			dp[j] = max(dp[j], dp[j - arr[i].price] + arr[i].value);
		}
	}

	cout << dp[c] << endl;

	return 0;
}

3.求解添加最少括号数的问题
括号序列由(){} [],但是(}{),(}(}不合法 ,编写程序求出这些不合法的括号序列至少需要添加几个括号
例如(}(} 需要添加四个括号变成合法的

分析:(只是根据下面的代码整理的思路,有些问题依然不是很明白,比如为什么最后还要有for循环,不加这个for循环行不行?不行,运行结果是错误的)
dp[i][j]表示的是从i到j至少需要的括号数量
如果s[i]与s[j]匹配了,那么dp[i][j]=dp[i][j-1]-1;
如果s[i]与s[j]不匹配
s[j]都是左括号( { [ ,那么 说明i-- j-1一定不会有括号和它匹配
dp[i][j]=dp[i][j-1]+1
s[j]右括号,不与s[i]匹配,这只是说明s[i]与s[j]一定不匹配,那么i+1 --j-1 可能会有和s[j]匹配的值
dp[i][j]=dp[i+1][j]+1
最后
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])

int func(string str){

	int ans;
	for (int i = 0; i < str.size(); i++)
	{
		dp[i][i] = 1;
	}
	for (int len = 1; len < str.size(); len++)
	{
		for (int i = 0; i < str.size() - len; i++)
		{
			int j = len + i;
			dp[i][j] = INF;
			if (str[i] == '(' && str[j] == ')' || str[i] == '[' && str[j] == ']' || str[i] == '{' && str[j] == '}')
			{
				dp[i][j] = min(dp[i][j], dp[i + 1][j - 1]);
				//cout << dp[i][j] << endl << dp[i + 1][j - 1] << endl;
			}
			//下面的两个if代码与给出的标准答案略有不同,因为标准答案不太理解,这个运行出来也是正确的,我也是可以理解的
			else if (str[j] == '(' || str[j] == '{' || str[j] == '[')
				dp[i][j] = min(dp[i][j], dp[i][j-1] + 1);
			else if (str[j] == ')' || str[j] == '}' || str[j] == ']')
				dp[i][j] = min(dp[i][j], dp[i+1][j] + 1);
			for (int k = i; k < j; k++)
				dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
		}
	}
	return dp[0][str.size() - 1];
}

4.求解小易喜欢的数列问题
1,k ,n 表示数列在1-k内,共n个数
2, 数列内,A<=B或者AmodB!=0

分析:
完全背包问题
状态转移公式
dp[i][j]=dp[i-1][m] (m在1—j-1之间) 表示的是有k个数但是以j 结尾的数列的合法个数
因为直接计算需要的是三层for循环,会超时所以可以这样计算
先计算选取i-1个数字的时候合法数列的总数sum,
而j加入到i-1个数后面的时候非法的数列的个数invalid
dp[i][j]=sum-invalid
初始状态
dp[1][n]=1 //以n结尾只有1个数字的数列只有1个

int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 1; i <= k; i++)
		dp[1][i] = 1;
	for (int i = 2; i <= k; i++)
	{
		//首先计算一下dp[i-1][...]一共有多少
		int sum = 0;
		for (int x
 = 1; x <= k; x++)
			sum += dp[i - 1][x];
		
		for (int j = 1; j <= k; j++)
		{
			int m = 1;
			int invalid = 0;
			while (m <= k)
			{
				if (!(m <= j || m % j != 0))
					invalid += dp[i - 1][m];
				m++;
			}
			dp[i][j] = sum - invalid;
		}
		
	}
	int ans = 0;
	for (int i = 1; i <= k; i++)
	{
		ans += dp[n][i];
	}
	cout << ans << endl;
	return 0;
}

需要注意的问题
1.invalid的计算,在m后面加上j不合法的时候,其不合法的数量为dp[i-1][m]个
所以是invalid+=dp[i-1][m]
2. 最后的答案是dp[n][…] 而不是dp[n][k]
因为让计算的是在1-k的数字之内数列长度为n的满足条件的数列个数
dp[n][k]计算的是以k结尾的数列长度为n的数列,这只是其中一个情况,还有dp[n][k-1],dp[n][k-2],这都满足在1-k之内数列长度为n的条件

5求解硬币分配问题
典型的完全背包问题
现有1‘ 2’ 5‘硬币,求解组成10’硬币的组合方案数
分析:
dp[i][j]表示前i个数字j‘的个数
选i dp[i][j]=dp[i-1][j-v[i]]
不选i dp[i][j]=dp[i-1][j]
dp[i][j]=dp[i-1][j-v[i]]+dp[i-1][j]
dp[j]=dp[j-v[i]]+dp[j]

int main()
{
	int ans = 0;
	//对dp初始化
	
	dp[0] = 1;
  	for (int i = 0; i < 3; i++)
	{
		for (int j = arr[i]; j <= p; j++)
			dp[j] = dp[j - arr[i]] + dp[j];
	}
	cout << dp[p] << endl;
	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值