上一题链接: C/C++百题打卡[8/100]——七段码[不用dfs的题解、蓝桥杯2020省赛].
百题打卡总目录: 🚧 🚧 …


一、题目总述

在这里插入图片描述
运行限制
  最大运行时间:1s
  最大运行内存:256M



题目难度:⭐️⭐️⭐️⭐️⭐️


二、题目解析 + 完整代码

解法一:数据结构(队列queue) + 算法(bfs)

解法二:数据结构(普通数组) + 算法(动态规划)

解法三:数据结构(集合set + 数组vector) + 算法(无)


● 首先说 解法一:数据结构(队列queue) + 算法(bfs)。这也是我最先想到的,后面两种都是看了别人的思路后,自己重写的一遍。算法步骤
  [1] 初始化一个装 int 变量的队列Q
  [2] 每输入一个新的砝码 w,计算这个 w 与队列 Q 里面所有的 已有的重量,如果 计算出来的值 是一个新的 重量(队列中没有),就装进去。
  [3] 值得一提的是,不仅要装上一步中 符合要求的重量,还要再装一遍 Q中每一个已有的重量
  [4] 还有就是,我们在实际编程中还需要另外一个临时队列 tmp_Q,至于为什么参见代码注释。

对 [3] 的解释:因为对于题目中的样例而言,有 3 个砝码(1, 4, 6),没有 [3] 的话,就不可能出现 “7=1+6” 的情况。

#include<iostream>
#include<queue>
#include<cmath>
using namespace std;

queue<int> Q;
int N;
int w;
int cnt;
bool ans[1000010];

int main()
{
	cin >> N;
	Q.push(0);
	for (int i = 0; i < N; i++)
	{
		cin >> w;
		queue<int> tmp_Q;
		while (!Q.empty())
		{
			int tmp = Q.front();
			Q.pop();
			if (ans[tmp + w] == false)		// 左边加新的砝码
			{
				ans[tmp + w] = true;
				cnt++;		// 答案数+1
				tmp_Q.push(tmp + w);
			}
			if (ans[abs(tmp - w)] == false)	// 右边加新的砝码
			{
				ans[abs(tmp - w)] = true;
				cnt++;		// 答案数+1
				tmp_Q.push(abs(tmp - w));
			}
			tmp_Q.push(tmp);	// 重装一遍 Q 里的每一个“元素”
		}
		Q = tmp_Q;	// 执行到这里的时候, Q 已经为空了, 重新赋值一遍.
					// 如果没有 tmp_Q, 即前面的 tmp_Q 全部换成 Q, 那么 while() 循环就一直跳不出来!
	}
	if (ans[0] == true)	
		cout << cnt - 1 << endl;	// 重量为 0 的情况不要
	else
		cout << cnt << endl;
	return 0;
}

● 其次说 解法二:数据结构(普通数组) + 算法(动态规划)。感觉能想到这种思路的话,那可能对 “背包问题” 有一定的见解。算法步骤
  [1] 初始化一个 bool 类型的普通数组 a[i][j]i 代表的是“装入前 i 个砝码”,j 代表的是“重量j”,a[i][j] 代表的是 “装入前 i 个砝码后,能否凑成重量为 j 的情况,能凑成的话,值就为 true,否则为 false”。
  [2] 有 N 个砝码,就遍历 N 遍,每遍装入一个新的砝码。在每次遍历中要做 [3] 和 [4] 两件事。
  [3] 首先,继承上一轮的结果。因为对于 “前 i 个砝码能凑成的所有重量的集合” ,那么前 i+1 个砝码一定也能凑到。
  [4] 然后遍历继承上一轮结果的 a[i][j](只有 j 在变),然后我们就去看,在 a[i][j] ==false 的情况下,是否能通过某种手段(代码中的三种)使得 a[i][j] 变为 true

#include<iostream>
#include<cmath>
using namespace std;

int N;
int a[101];
int cnt;
bool ans[101][1000010];

int main()
{
	cin >> N;
	int tol = 0;

	for (int i = 1; i <= N; i++)	
	{
		cin >> a[i];
		tol += a[i];	
	}

	for (int i = 1; i <= N; i++)
	{
		// 继承上一轮的结果。因为对于 “前 i 个砝码能凑成的所有重量的集合”, 则前 i+1 个砝码一定也能凑到
		for (int j = 1; j <= tol; j++)	
			ans[i][j] = ans[i - 1][j];

		for (int j = 1; j <= tol; j++) 	// 只需要遍历到 tol
		{
			if (ans[i][j] == false)
			{
				if (a[i] == j)		// 手段一:新加入砝码的重量 正好为 j
					ans[i][j] = true;
				else if (ans[i-1][j + a[i]] == true)		// 手段二(背包的思想)
					ans[i][j] = true;
				else if (ans[i-1][abs(j - a[i])] == true)	// 手段三(背包的思想)
					ans[i][j] = true;
			}
		}
	}
	for (int i = 1; i <= tol; i++) // 从重量为 1 的情况开始计数
	{
		if (ans[N][i] == true)		// 对于前 N 个砝码, 能凑成重量为 i 的情况的话, 计数就 + 1
			cnt++;
	}
	cout << cnt << endl;
	return 0;
}

● 最后说 解法三:数据结构(集合set + 数组vector) + 算法(无)。感觉要用这种方法的话,那可能得对 “数据结构” 有一定的见解,但是一想到就很简单!算法步骤
  [1] 初始化一个 装 int 类型的变量的集合 set,因为 set 自带 “去重” 的功能,那么它就为我们省去了很多关于 “判断” 的事。
  [2] 和 解法一 类似,我们需要用一个临时的中间容器 数组verctor 来辅助,原因详见代码。

#include<iostream>
#include<vector>
#include<set>
#include<cmath>
using namespace std;
int N;
int w;
set<int> s;
int main()
{
	cin >> N;
	for (int i = 1; i <= N; i++)
	{
		cin >> w;
		vector<int> v(s.begin(), s.end());	
		for (int i = 0; i < v.size(); i++)	// 如果没有 v, 即前面的 v 全部换成 s, 那么 for() 循环就一直跳不出去!
		{
			s.insert(w + v[i]);
			if( abs(w - v[i])!=0 )	 		// 我们不要 组合重量为 0 的情况
				s.insert(abs(w - v[i]));
		}
		s.insert(w);
	}
	cout << s.size() << endl;	// 集合 s 中装了不同的 “组合重量” 的数量即是答案
	return 0;
}


三、运行结果比较

在这里插入图片描述

效率:解法一 > 解法二 > 解法三。



四、做题小结与感慨

● 虽然说我用了三种不同的 “数据结构+算法” 来解这道题,但是当我都实现了它们三的时候,其实发现它们内部是由内在联系的。都有一个 “继承” 的思想。分别体现在 “临时队列”、“继承数组”、“临时vector” 当中。

● 还有就是,“解法三” 真的好香,因为 “集合” 自带 “去重” 功能,所以能事半功倍。所以灵活取用数据结构很重要呀。



五、参考附录

[1] 原题地址:https://www.lanqiao.cn/problems/1447/learning/.

上一题链接: C/C++百题打卡[8/100]——七段码[不用dfs的题解、蓝桥杯2020省赛].

百题打卡总目录: 🚧 🚧 …


C/C++百题打卡[9/100]——砝码称重[3种解法、蓝桥杯2021省赛]
标签:BFS、队列、动态规划、集合

不定期更新     
   2022/3/17     

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐