2020蓝桥杯G题 砝码称重(如何更好地理解dp 附一道洛谷题

本文介绍了一道关于动态规划的砝码称重问题,分析了两种不同的解题方法。法一是通过设置二维bool数组,表示使用前i个砝码能否组成重量j,递归地更新数组;法二则更为简洁,利用或运算直接更新数组。题目要求计算能称出的不同重量种类,而不仅仅是方法数。此外,还提供了另一道简单的动规题目作为理解辅助。
摘要由CSDN通过智能技术生成

2020蓝桥杯G题 砝码称重

【问题描述】
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1, W2, · · · , WN。
请你计算一共可以称出多少种不同的重量?
注意砝码可以放在天平两边。
【输入格式】
输入的第一行包含一个整数 N。
第二行包含 N 个整数:W1, W2, W3, · · · , WN。
【输出格式】
输出一个整数代表答案。
【样例输入】
3
1 4 6
【样例输出】
10
【样例说明】
能称出的 10 种重量是:1、2、3、4、5、6、7、9、10、11。
1 = 1;
2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
9 = 4 + 6 − 1;
10 = 4 + 6;
11 = 1 + 4 + 6。
【评测用例规模与约定】
对于 50% 的评测用例,1 ≤ N ≤ 15。
对于所有评测用例,1 ≤ N ≤ 100,N 个砝码总重不超过 100000。

通过观察题目可以发现这是一道动规题,但和常规的动规题有一点区别,这道动规题含有减法项。
我的分析方法是,首先看所给变量参数,我们这里有:

砝码 i
每个砝码的重量w[i] (可以求得sum

法一

先来说法一,该方法是套用 dp 模板,更好理解一些 。首先设dp[i][j] ,其中 **i 表示第 i 个砝码,j 表示前 i 个砝码刚好组成 j 重量的方法数。**接下来分析可以产生的两种情况 即:

1,如果 当前 j == w[i] ,即第 i 个砝码的重量正好等于 j 重量。那么只称重第 i 个砝码为一个方法数,再加上前 i-1个砝码组成 j 的方法数可以得到 dp[i][j]。
.
2,如果 j != w[i] ,即第 i 个砝码的重量可能小于或者大于需要组成的 j 的重量 ,那么我们可以选择称重该砝码或者不称重该砝码。假如不选,这个时候前 i 个砝码可以组成 j 重量的方法数为dp[i-1][j] ,假如选,这个时候前i-1个砝码所组成的重量是j-w[i], 也就是说前i个砝码中所有能凑出j-w[i]重量的方案再加上当前第i个砝码,就可以变成前i个砝码所组成重量为 j 的方案数。两种情况加起来可以得到 dp[i][j] 。

  • if (j == w[i]) f[i][j] = f[i-1][j] + 1;
  • else f[i][j] = f[i-1][j] + f[i-1][abs(w[i]-j)];

上代码

#include<bits/stdc++.h> 
using namespace std;
int n;
bool f[110][100050];
int w[110];
int sum=0;
int ans=0;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i];
		sum+=w[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=sum;j++){
			if(j==w[i]) f[i][j] = f[i-1][j]+1;
			else f[i][j] = f[i-1][j]+f[i-1][abs(w[i]-j)];
		}
	}
	for(int j=1;j<=sum;j++){
		ans+=f[n][j];
	}
	cout<<ans<<endl;
//	for(int j=1;j<=sum;j++)	{
//		cout<<f[n][j]<<endl;
//	}		//可以把bool型改成int型数组,测试输出的方案数
}

这道题和常规的动规题求的结果不同,题目要求求可以组成多少种砝码的重量,而不是求方法数。所以把 int 型 改成 bool 型即可

法二

法二对于该题更加简洁一些,理解起来和法一类似,这里就不过多赘述了。

  • f[i][j] = f[i-1][j]||f[i-1][abs(j-w[i])];

直接附上代码:

#include<bits/stdc++.h>
using namespace std;
int n;
bool f[110][100050];
int w[110];
int sum = 0;
int ans = 0;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i];
		sum+=w[i];
	}
	f[0][0] = 1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=sum;j++){	//特别注意这里从0开始
			f[i][j] = f[i-1][j]||f[i-1][abs(j-w[i])];	//一种情况为true就为true
		}
	}
	for(int j=1;j<=sum;j++){
		ans+=f[n][j];
	}
	cout<<ans<<endl;
}

ps:动规也可以用搜索写,不过一般会超时。

洛谷水题 小A点菜

为了便于理解动规,这里附上一道洛谷的简单题 洛谷P1164
题意很简单,给出 n 道菜 m块钱,刚好花完m块钱有几种菜品组合

输入:
4 4
1 1 2 2
输出:
3

也是分三种情况,等于小于和大于。解释和上述法一基本相同,可以参考法一进行理解。
直接附上代码:

#include<bits/stdc++.h>
using namespace std;
int n;
int sum;
int p[150];
int f[150][100050]={0};
int ans = 0;

int main(){
	cin>>n>>sum;
	for(int i=1;i<=n;i++){
		cin>>p[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=sum;j++){		//j相当于总钱数 
			if(j == p[i]) f[i][j] = f[i-1][j]+1;
			else if(j>p[i]) f[i][j] = f[i-1][j]+f[i-1][j-p[i]];		//不吃或者吃 
			else if(j<p[i]) f[i][j] = f[i-1][j];
		}
	}

	cout<<f[n][sum]<<endl;
} 

通过这两道题可以对动规的模板有一个进一步的理解。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值