多重部分和问题(多重背包+二进制优化)

本文探讨了一个特定的编程问题,即通过选择不同数量的数字使其总和等于给定值K的问题。该问题被转化为01背包问题,并采用二进制优化技巧进行高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

时间限制: 1 Sec  内存限制: 64 MB
提交: 18  解决: 14

题目描述

有n种不同大小的数字,每种各个。判断是否可以从这些数字之中选出若干使它们的和恰好为K。

输入

首先是一个正整数T(1<=T<=100)
接下来是T组数据
每组数据第一行是一个正整数n(1<=n<=100),表示有n种不同大小的数字
第二行是n个不同大小的正整数ai(1<=ai<=100000)
第三行是n个正整数mi(1<=mi<=100000),表示每种数字有mi个
第四行是一个正整数K(1<=K<=100000)

输出

对于每组数据,如果能从这些数字中选出若干使它们的和恰好为K,则输出“Yes”,否则输出“No”,每个输出单独占一行

样例输入

2
3
3 5 8
3 2 2
17
2
1 2
1 1
4

样例输出

Yes
No
思路:有mi个啊ai,则可以把每个ai看成独立的数,比如例子上有3个3,2个5,2个8,我们可以看成7个独立的数,看看
把这7个数怎样组合可以得到k,这就变成了01背包问题。思想懂了就要优化了,想想依稀有100个数,没个数也有100,
一共就有10000个数,这样肯定会超时,所以用到了二进制优化,比如有20个3,就可以把3 分成1,2,4,8,5个3 等。
然后20以内的数都可以用这几个数表示。
100以内都可以用1,2,4,8,16,32,37个数来表示,
#include <stdio.h>
#include <string.h>
#include <algorithm>
int dp[100005];
int a[105],w[105];
int main()
{
	using namespace std;
	int t;
	scanf("%d",&t);
	int n;
	int k;
	int i,j,l;
	int ans;
	while(t--)
	{
		scanf("%d",&n);
		for(i=1; i <= n; i++)
		{
			scanf("%d",&a[i]);
		}
		for(i=1; i <= n; i++)
		{
			scanf("%d",&w[i]);
		}
		scanf("%d",&k);
		memset(dp,0,sizeof(dp));
		for(i=1; i<=n; i++)
		{
			if(dp[k]==k)
			{
				break;
			}
			if(a[i]*w[i] >= k)//如果它大于K,则在k范围内可以看成这个数是无穷的 
			{
				for(j=a[i]; j <=k; j++)//所以j可以从下到大累加 
				{
					dp[j] = max(dp[j-a[i]]+a[i] , dp[j]);
				}
			}
			else
			{
				ans=1;
				while(w[i] >= ans)
				{
					for( j=k; j >= ans*a[i]; j--)
					{
						dp[j] = max(dp[j-ans*a[i]]+ans*a[i] , dp[j]);
					}
					w[i] -= ans;
					ans <<= 1;//相当于ans *= 2;但是比它快。 
				}
				for( j=k; j >= w[i]*a[i]; j--)
				{
					dp[j] = max(dp[j-w[i]*a[i]]+w[i]*a[i] , dp[j]);
				}
			}
		}
		if(dp[k]==k)
		{
			printf("Yes\n");
		}
		else
		{
			printf("No\n");
		}
	}
	return 0;
}





### 多重背包问题中的二进制优化 对于多重背包问题,当每种物品的数量较大时,直接枚举所有可能的选择会导致极高的时间复杂度。为了降低这种复杂度并提高效率,引入了二进制优化方法。 #### 朴素方法的时间复杂度过高 在处理大量相同数量的物品时,比如每种物品有2000个,总数达到1000种不同类型的物品,每个物品体积为2000单位的情况下,使用简单的枚举方式会使数据量变得异常庞大,使得常规算法难以承受其计算负担[^1]。 #### 二进制分解策略 通过将每个物品按照二进制位拆分来表示不同的组合数,可以有效减少需要考虑的状态数目。具体来说,假设某类物品最多可选`S`件,则可以通过一系列形如\( \{2^0, 2^1,...,2^{k-1}, S - (2^k - 1)\} \) 的子集覆盖所有的选取可能性,其中 \( k=\lfloor\log_2(S)\rfloor\) 。这种方法不仅简化了状态转移方程的设计,还大大降低了整体运算次数[^3]。 #### C++实现示例 以下是基于上述理论改进后的C++代码片段: ```cpp #include <iostream> using namespace std; const int N = 1e5 + 7; int dp[N]; struct Item { int v, w, s; }; void binary_optimization(Item items[], int n, int m) { vector<Item> new_items; for (int i = 0; i < n; ++i) { int num = items[i].s; for (int cnt = 1; ; cnt <<= 1) { if (cnt > num) break; new_items.push_back({items[i].v * cnt, items[i].w * cnt}); num -= cnt; } if (num > 0) new_items.push_back({items[i].v * num, items[i].w * num}); } memset(dp, 0, sizeof(dp)); for (auto& item : new_items) for (int j = m; j >= item.v; --j) dp[j] = max(dp[j], dp[j - item.v] + item.w); } int main() { int n, m; cin >> n >> m; Item items[n]; for (int i = 0; i < n; ++i) cin >> items[i].v >> items[i].w >> items[i].s; binary_optimization(items, n, m); cout << dp[m] << endl; } ``` 此版本利用了动态规划的思想,在遍历过程中不断更新最优解,从而实现了更高效的求解过程[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值