【 OJ 】 HDOJ1059 多重背包求大理石分配问题 [ 51 ]

本文深入探讨了多重背包问题的各种解法,包括直接DFS暴力搜索、动态规划和母函数方法。通过对每种方法的分析和代码示例,揭示了多重背包问题的解决策略,特别关注于如何有效地筛选最大价值。

Time Limit Exceeded

直接DFS暴搜.....死的很安详,然后写了母函数的解法,死的也很安详超时(:逃 江湖传言 sum取模60可AC多重背解未被AC

关于多重背包的解法不想说啥,这里就是将承重m换成了价值v来理解筛选,用价值V来刷选最大可以筛选出Vmax及dp[V]=V,如果不等说明这个价值不可以被组合选举出来,其中多重背包二进制转化01背包也有很详细的讲解...

如果不懂可以看我的自学笔记关于背包问题的,我从不会到会的所有理解,非常详细......

(:机票  https://blog.youkuaiyun.com/QingCoffe/article/details/85239055 如果你不会01背包问题,我想你看完的收益会很大

01/多重/完全/背包问题等 (:逃 

多重背包解法:(没被AC搞不懂为啥,用可AC代码自检,发现代码输出结果没问题.....不知道哪里出了问题懒得搞了)

# include<iostream>
# include<algorithm>
#pragma warning (disable:4996)
using namespace std;
int n[6];
int val[20010],dp[60020];
int index;
void OUT(bool b) {
	static int index = 0;
	printf("Collection #%d\n", ++index);
	if (b) {
		printf("Can be divided.\n\n");
	}
	else {
		printf("Can't be divided.\n\n");
	}
}
int INput(void) {
	int total_ = 0;
	for (int i = 0; i < 6; i++) {
		scanf("%d", &n[i]);
		total_ += (i + 1)*n[i];
		for (int j = 1; j <= n[i]; j <<= 1) {
			val[index++] = (i + 1)*j;
			n[i] -= j;
		}
		if (n[i]) {
			val[index++] = (i + 1)*n[i];
		}
	}
	return total_;
}
int main(void) {
	int total;
	int index = 0;
	while (1) {
		index = 0;
		total = INput();//录入下一次信息
		if (!total)break;//全0
		if (total & 1)OUT(false);//总价值为奇数不可分割
		else {//尝试分割
			total >>= 1;
			int i, j;
			memset(dp, 0, sizeof(dp));//初始化dp数组
			for (i = 0; i < index; i++) {//物品从第一个一次排列
				for (j = total; j >= val[i]; j--) {//01倒序
						dp[j] = max(dp[j], dp[j - val[i]] + val[i]);
				}
			}
			if (dp[total] == total)OUT(true);
			else OUT(false);
		}
	}
	system("pause");
	return 0;
}

母函数:

# include<iostream>
# include<algorithm>
# define Inf 120000
using namespace std;
int n[6];
int a[Inf], b[Inf];
void OUT(bool b) {
	static int index = 0;
	if (index)cout << endl;
	cout << "Collection #" << ++index << ":" << endl;
	if (b) {
		cout << "Can be divided." << endl;
	}
	else {
		cout << "Can't be divided." << endl;
	}
}
int INput(void) {
	int total_ = 0;
	for (int i = 0; i < 6; i++) {
		cin >> n[i];//录入弹珠信息
		total_ += (i + 1)*n[i];
	}
	return total_;
}
int main(void) {
	int total = INput();//拿到第一个输入的价值
	while (!((*max_element(n, n + 6)) == 0)) {//全0不做
		if (total & 1)OUT(false);//总价值为奇数不可分割
		else {//尝试分割
			total /= 2;
			memset(a, 0, sizeof(a));
			memset(b, 0, sizeof(b));
			int i, j, k;
			for (i = 0; i<=total&&i <= n[0]; i++) a[i] = 1;
			for ( i = 1; i < 6; i++) {//共6个表达式,从第2个表达式到第6个表达式
				if (!n[i])continue;//个数为0,第i个表达式不存在
				for ( j = 0; j <= total; j++) {//计算出的表达式指数从0循环到total
					for (k = 0; a[j] && k <= total&&k <= (n[i] * (i + 1)); k += i+1) {//for (k = 0;k <= coin[i] * i; k += i)
						if (j + k > total)break;
						b[j + k] += a[j];
					}
				}
				for (k = 0; k <= total; k++) {
					a[k] = b[k]; b[k] = 0;
				}//刷新数组a的值,b数组清零
			}
			if (a[total])OUT(true);
			else OUT(false);
		}
		total = INput();//录入下一次信息
	}
	system("pause");
	return 0;
}

DFS暴搜:

# include<iostream>
# include<algorithm>
//# include<Windows.h>
using namespace std;
int n[6] = { 0 };
int ans, total, flag;
int Visit[6];
void OUT(bool b) {
	static int index = 0;
	if (index)cout << endl;
	cout << "Collection #" << ++index << ":" << endl;
	if (b) {
		cout << "Can be divided." << endl;
	}
	else {
		cout << "Can't be divided." << endl;
	}
}
int INput(void) {
	int total_ = 0;
	for (int i = 0; i < 6; i++) {
		cin >> n[i];//录入弹珠信息
		Visit[i] = n[i];
		total_ += (i+1)*n[i];
	}
	return total_;
}
void Fen(int index) {//index 层数一共6层,i第几个物品 n[i]为该层有的物品数
	for (int i = index; i < 6 && !flag; i++) {
		if (!index) {//0 层 ans =0;初始化访问表
			ans = 0;
			for (int j = 0; j < 6; j++) {
				Visit[j] = n[j];
			}	//初始化访问表
		}
		if (!Visit[i])continue;
		for (int k = 0; k <= n[i]; k++) {//首先不拿---直到拿n[i]个
			Visit[i] -= k;//先拿K个
			ans += (i + 1)*k;	//得到K个总价值
			if (ans < (total / 2)) {//价值小于目标数
				Fen(i + 1);//递归到下一层
				ans -= (i + 1)*k; //拿回的东西放回去
				Visit[i] += k;
			}
			else if (ans == total / 2) {
				flag = true; return;//找到并返回
			}
			else {//价格已经超出了 剪枝
				ans -= (i + 1)*k; //东西放回去 结束
				Visit[i] += k;
				return;
			}
		}

	}
}
int main()
{
	total = INput();//拿到第一个输入的价值
	while (!((*max_element(n, n + 6)) == 0)) {//全0不做
		if (total & 1)OUT(false);//总价值为奇数不可分割
		else {//尝试分割
			flag = false;//未找到
			Fen(0);//从0开始找
			if (flag)OUT(true);
			else OUT(false);
		}
		total = INput();//录入下一次信息
	}
	system("pause");
	return 0;
}

因为超时所以不知道准确答案,因此我写了个检测......用的别人正确的AC码做参考:随机生成了10W个数据显示是对的

# include<iostream>
# include<algorithm>
#include <cstdio>
#include <cstring>
//# include<Windows.h>
using namespace std;
int n[6] = { 0 };
int a[6];
int ans, total, flag;
int Visit[6];
const int SIZE = 120000 + 16;
int dp[SIZE];
void Fen(int index) {//index 层数一共6层,i第几个物品
	for (int i = index; i < 6 && !flag; i++) {
		if (!index) {//0 层 ans =0;初始化访问表
			ans = 0;
			for (int j = 0; j < 6; j++) {
				Visit[j] = n[j];
			}	//初始化访问表
		}
		if (!Visit[i])continue;
		for (int k = 0; k <= n[i]; k++) {//首先不拿---直到拿n[i]个
			Visit[i] -= k;//先拿K个
			ans += (i + 1)*k;	//得到K个总价值
			if (ans < (total / 2)) {//价值小于目标数
				Fen(i + 1);//递归到下一层
				ans -= (i + 1)*k; //拿回的东西放回去
				Visit[i] += k;
			}
			else if (ans == total / 2) {
				flag = true; return;//找到并返回
			}
			else {//价格已经超出了 剪枝
				ans -= (i + 1)*k; //东西放回去 结束
				Visit[i] += k;
				return;
			}
		}

	}
}
void RAND(void) {
	total = 0;
	for (int i = 0; i < 6; i++) {
		n[i] = rand() % 5 + 1;
		a[i] = n[i];
		Visit[i] = n[i];
		total += (i + 1)*n[i];
	}
}
bool My(void) {
	
	if (total & 1)return false;//总价值为奇数不可分割
	else {//尝试分割
		flag = false;//未找到
		Fen(0);//从0开始找
		if (flag)return true;
		else return false;
	}
}
bool check()
{
	for (int i = 0; i<6; i++)
		if (a[i] != 0)
			return true;
	return false;
}
bool YO(void) {
	int sum;
	int t = 0;
	while (true)
	{
		sum = 0;
		for (int i = 0; i<6; i++)
		{
			sum += (i + 1)*a[i];
		}
		if (!check())
			break;
		if (sum % 2 == 1)
		{
			return false;
		}
		else
		{
			memset(dp, -1, sizeof(dp));
			int k = sum / 2;
			dp[0] = 0;
			for (int i = 0; i<6; i++)
			{
				for (int j = 0; j <= k; j++)
				{
					if (dp[j] >= 0)
					{
						dp[j] = a[i];
					}
					else if (j<(i + 1) || dp[j - (i + 1)] <= 0)
					{
						dp[j] = -1;
					}
					else
					{
						dp[j] = dp[j - (i + 1)] - 1;
					}
				}
			}
			if (dp[k] >= 0)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}
}
int main()
{
	bool re1, re2;
	int index = 0;
	while((index+1)<10000){
		cout << ++index << endl;
		RAND();
		re1 = My();
		re2 = YO();
		if (re1 != re2) {
			cout << "出现不同结果:" << endl;
			cout << "--------------------" << endl;
			cout << "MY:" << re1 << endl;
			cout << "--------------------" << endl;
			cout << "--------------------" << endl;
			cout << "YO:" << re2 << endl;
			cout << "--------------------" << endl;
			cout << "--------------------" << endl;
			cout << "n 的情况: " << endl;
			for (int i = 0; i<6; i++)
			{
				cout <<i+1<<" : "<< a[i] << "   "<<n[i]<<endl;
			}
			cout << "--------------------" << endl;
			getchar();
		}
	}
	
	system("pause");
	return 0;
}

 

### 关于HNUCM-OJ平台上的0-1背包问题是经典的动态规划问题之一,在给定容量的背包下,如何选择物品使得所选物品的价值最大。每个物品要么被完全放入背包中,要么不放,不允许分割。 #### 题目描述 假设有一个容量为`W`的背包以及若干个不同重量和价值的物品。每种物品仅有一件,可以选择是否将其放入背包内。目标是在不超过背包容量的前提下使背包容纳的物品总价值最大化。 具体来说,对于第`i`个物品有对应的重量`w[i]`和价值`v[i]`,其中`i∈[1,n]`表示共有`n`个不同的物品可供挑选。现在要计算能获得的最大价值是多少? #### 动态规划解法思路 定义状态转移方程如下: 设`dp[j]`代表当背包容量为`j`时可以达到的最大价值,则状态转移关系可表述为: \[ dp[j]=\max(dp[j],dp[j-w_i]+v_i)\quad \text{for all}\ j>=w_i \] 初始条件设置为`dp[0]=0`,即没有任何空间的情况下所能获取到的最大价值自然就是零;而对于其他的`dp[j] (j>0)`初始化为负无穷大(-∞),意味着这些位置暂时还没有找到可行解。 通过上述方式构建二维数组来保存中间结果,并最终返回最后一项作为答案。 #### Java实现代码示例 ```java import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); // 物品数量 int W = sc.nextInt(); // 背包容量 int[] weight = new int[N + 1]; int[] value = new int[N + 1]; for (int i = 1; i <= N; ++i){ weight[i] = sc.nextInt(); value[i] = sc.nextInt(); } int[][] dp = new int[N + 1][W + 1]; for (int i = 1; i <= N; ++i) for (int j = 1; j <= W; ++j) if (weight[i] > j) dp[i][j] = dp[i - 1][j]; else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); System.out.println(dp[N][W]); } } ``` 此段代码实现了基于动态规划算法解决0-1背包问题的方法[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值