OpenJudge NOI 2.1 8755:砝码称重

【题目链接】

OpenJudge NOI 2.1 8755:砝码称重

【题目考点】

1. 枚举

2. 深搜

【解题思路】

1. 枚举

设重量为1g、2g、3g、5g、10g、20g的砝码的总数量分别为: a 1 , a 2 , a 3 , a 5 , a 10 , a 20 a_1, a_2, a_3, a_5, a_{10}, a_{20} a1,a2,a3,a5,a10,a20,选择使用的砝码数量分别为: i 1 , i 2 , i 3 , i 5 , i 10 , i 20 i_1, i_2, i_3, i_5, i_{10}, i_{20} i1,i2,i3,i5,i10,i20
设bool类型的vis数组,vis[i]为真表示可以称出重量i。

  • 枚举对象: i 1 , i 2 , i 3 , i 5 , i 10 , i 20 i_1, i_2, i_3, i_5, i_{10}, i_{20} i1,i2,i3,i5,i10,i20
  • 枚举范围:
    0 ≤ i 1 ≤ a 1 0\le i_1 \le a_1 0i1a1
    0 ≤ i 2 ≤ a 2 0\le i_2 \le a_2 0i2a2
    0 ≤ i 3 ≤ a 3 0\le i_3 \le a_3 0i3a3
    0 ≤ i 5 ≤ a 5 0\le i_5 \le a_5 0i5a5
    0 ≤ i 10 ≤ a 10 0\le i_{10} \le a_{10} 0i10a10
    0 ≤ i 20 ≤ a 20 0\le i_{20} \le a_{20} 0i20a20
  • 判断条件:
    当前可以称出的重量为 i 1 + 2 i 2 + 3 i 3 + 5 i 5 + 10 i 10 + 20 i 20 i_1+2i_2+3i_3+5i_5+10i_{10}+20i_{20} i1+2i2+3i3+5i5+10i10+20i20,在vis数组中记录可以称出该重量。

可以称出的重量最小为1,最大为1000。最后遍历vis数组,看下标1~1000中有多少个元素为真,统计可以称出的重量的个数。

复杂度分析
已知所有砝码总重量不超过1000,即
a 1 + 2 a 2 + 3 a 3 + 5 a 5 + 10 a 10 + 20 a 20 ≤ 1000 a_1+2a_2+3a_3+5a_5+10a_{10}+20a_{20}\le 1000 a1+2a2+3a3+5a5+10a10+20a201000
每层循环都是从0遍历到该砝码的总数量,所以循环次数为 1 + a i 1+a_i 1+ai
( 1 + a 1 ) ( 1 + a 2 ) ( 1 + a 3 ) ( 1 + a 5 ) ( 1 + a 10 ) ( 1 + a 20 ) (1+a_1)(1+a_2)(1+a_3)(1+a_5)(1+a_{10})(1+a_{20}) (1+a1)(1+a2)(1+a3)(1+a5)(1+a10)(1+a20)的最大值。
该问题可以使用动态规划的方法求解:
w 1 = 1 , w 2 = 2 , w 3 = 3 , w 4 = 5 , w 5 = 10 , w 6 = 20 w_1=1, w_2=2, w_3=3, w_4=5, w_5=10,w_6=20 w1=1,w2=2,w3=3,w4=5,w5=10,w6=20
a i a_i ai表示重量为 w i w_i wi的砝码数量。

  • 集合: a 1 ∼ a 6 a_1\sim a_6 a1a6这6个数字所有可能的取值
  • 限制:关注前i个数字, ∑ k = 1 i a k w k \sum_{k=1}^ia_kw_k k=1iakwk小于等于多少
  • 属性:乘积式 ∏ k = 1 i ( 1 + a k ) \prod_{k=1}^{i}(1+a_k) k=1i(1+ak)
  • 条件:最大
  • 统计量:乘积式 ∏ k = 1 i ( 1 + a k ) \prod_{k=1}^{i}(1+a_k) k=1i(1+ak)

状态定义dp[i][j] a 1 w 1 + a 2 w 2 + . . . + a i w i ≤ j a_1w_1+a_2w_2+...+a_iw_i\le j a1w1+a2w2+...+aiwij情况下 ( a 1 + 1 ) ( a 2 + 1 ) . . . ( a i + 1 ) (a_1+1)(a_2+1)...(a_i+1) (a1+1)(a2+1)...(ai+1)的最大值。
或者可以描述为:满足 ∑ k = 1 i a k w k ≤ j \sum_{k=1}^ia_kw_k\le j k=1iakwkj ∏ k = 1 i ( 1 + a k ) \prod_{k=1}^{i}(1+a_k) k=1i(1+ak)的最大值。
初始状态:j为0时,要让 a 1 w 1 + a 2 w 2 + . . . + a i w i ≤ 0 a_1w_1+a_2w_2+...+a_iw_i\le 0 a1w1+a2w2+...+aiwi0,那么 a 1 , a 2 , . . . , a i a_1, a_2, ..., a_i a1,a2,...,ai都要为0,此时乘积式 ( a 1 + 1 ) ( a 2 + 1 ) . . . ( a i + 1 ) (a_1+1)(a_2+1)...(a_i+1) (a1+1)(a2+1)...(ai+1)为1。因此dp[i][0]=1

集合:满足 a 1 w 1 + a 2 w 2 + . . . + a i w i ≤ j a_1w_1+a_2w_2+...+a_iw_i\le j a1w1+a2w2+...+aiwij a 1 , a 2 , . . . , a i a_1, a_2, ..., a_i a1,a2,...,ai的取值方案。

分割集合:根据 a i a_i ai的值来分割集合

  • a i = 0 a_i=0 ai=0dp[i][j] = dp[i-1][j]*(0+1)

  • a i = 1 a_i=1 ai=1dp[i][j] = dp[i-1][j-w[i]*1]*(1+1)

  • a i = 2 a_i=2 ai=2dp[i][j] = dp[i-1][j-w[i]*2]*(1+2)

  • a i = k a_i=k ai=kdp[i][j] = dp[i-1][j-w[i]*k]*(1+k)

  • k最小为0,最大为 ⌊ j / w i ⌋ \lfloor j/w_i \rfloor j/wi

  • 以上所有情况取最大值

#include <bits/stdc++.h>
using namespace std;
int dp[10][1005];//dp[i][j]:a[1]*w[1]+...+a[i]*w[i] <= j情况下(a[1]+1)*(a[2]+2)*...*(a[i]+1)的最大值。
int w[10] = {
    
    0, 1, 2, 3, 5, 10, 20};
int main()
{
    
    
	for(int i = 1; i <= 6; ++i)
		dp[i][0] = 1;
	for(int i = 1; i <= 6; ++i)
		for(int j = 1; j <= 1000; ++j)
			for(int k = 0; k <= j/w[i]; ++k)//j/w[i]在整除运算情况下得到的就是floor(j/w[i]) 
				dp[i][j] = max(dp[i][j], dp[i-1][j-w[i]*k]*(1+k));
	cout << dp[6][1000]; 
	return 0;
}

运行结果为:64827000, 是 1 0 7 10^7 107数量级。
总循环次数是 1 0 7 10^7 107数量级,是可以接受的。

2. 深搜

与枚举思路相似,将每种砝码的总数量放在一个数组a中,每层搜索可以确定选择一种砝码的数量,每种砝码的数量都确定后,看所有选择砝码的总重量。如果该重量没有出现过,则进行计数。
注意:不选择砝码总重量为0的情况也会被计数,需要将该情况去掉。
最后输出可以称出的重量的数量。

【题解代码】

解法1:枚举

#include <bits/stdc++.h>
using namespace std;
int main()
{
    
    
	bool vis[1005] = {
    
    };//vis[i]:重量i是否可以称出 
	int a1, a2, a3, a5, a10, a20, ct = 0;//ax:重量为x的砝码的总数量 ct:可以称出的重量的种类数
	cin >> a1 >> a2 >> a3 >> a5 >> a10 >> a20;
	for(int i1 = 0; i1 <= a1; ++i1)//ix:选择使用重量为x的砝码的数量
	for(int i2 = 0; i2 <= a2; ++i2)
	for(int i3 = 0; i3 <= a3; ++i3)
	for(int i5 = 0; i5 <= a5; ++i5)
	for(int i10 = 0; i10 <= a10; ++i10)
	for(int i20 = 0; i20 <= a20; ++i20)
		vis[i1+2*i2+3*i3+5*i5+10*i10+20*i20] = true;
	for(int i = 1; i <= 1000; ++i)
		if(vis[i])
			ct++;
	cout << "Total=" << ct;
	return 0;
}

解法2:深搜

#include <bits/stdc++.h>
using namespace std;
bool vis[1005];//vis[i]:重量i是否可以称出
int w[10] = {
    
    0, 1, 2, 3, 5, 10, 20};//w[i]:下标为i对应的砝码重量 
int a[10], ct;//a[i]:表示重量为w[i]的砝码的数量为a[i] 
int sum;//当前已选择砝码的总重量 
void dfs(int k)//确定选择重量为w[k]的砝码的数量
{
    
    
	if(k > 6)
	{
    
    
		if(vis[sum] == false)
		{
    
     
			vis[sum] = true;//已经确定了6种砝码的数量,记录当前总重量
		 	ct++;
		}
		return;
	}
	for(int i = 0; i <= a[k]; ++i)
	{
    
    
		sum += i*w[k];
		dfs(k+1); 
		sum -= i*w[k];
	}
} 
int main()
{
    
    
	for(int i = 1; i <= 6; ++i)
		cin >> a[i];
	dfs(1); 
	ct--;//减去总重量为0的情况 
	cout << "Total=" << ct;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/lq1990717/article/details/128629257