蓝桥杯 试题 算法训练 娜神平衡 C++ 详解

问题描述:

  娜娜是一个特别可爱的女孩子,作为学神的她最近在情感方面出现了一点点小问题。
  她暗恋的琦琦是一名学霸,他只喜欢长得漂亮和学习很好的女生。
  娜娜学习确实很神,但是她在琦琦面前却总是表现不出平时的神力。
  琦琦感受到了娜娜对他的爱,但是他还是觉得娜娜的学习并不是特别好,于是他出了一道题给娜娜。
  “娜娜,我们之间的关系需要在不断深入的同时保持一定的平衡,不可以你总是强势或者我总是弱势。”
  琦琦给了娜娜一些两两不等的数,希望娜娜能把这些数分成两组A和B,满足以下条件:
  1:每一次只能操作一个数,即只取出一个数分入A中或B中;
  2:每一次操作完成后,A中数之和与B中数之和的差不能超过r。

  新时代的丘比特们啊,帮帮娜娜吧!

输入格式:

  输入共两行。
  第一行包括两个正整数n和r,n表示琦琦一共给了n个数,r的意义见题目描述。
  第二行包括n个正整数,分别表示琦琦给的n个数。

输出格式:

  输出共两行,分别把A与B两组数按从小到大输出。
  注意输入中n个数的第一个必须分入A组。
  琦琦保证这样的输出唯一。

样例输入:

4 10
9 6 4 20

样例输出:

4 6 9
20

样例说明:

  先把4和6先后分入A组,再把20分入B组,最后把9分入A组。

数据规模和约定:

  很小,真的很小。


前言(上):

我一开始的解题思路是:不断加入新数据的同时,时刻保持"两人"平衡。

有新数据,能加给"娜娜"便直接加,不然加给"琦琦"。

"两人"平衡则继续等新数据,不然必是"琦琦强于娜娜",则重复"琦琦"中找出最小的元素"娜娜",直到"两人"再次平衡……

 但最多只有80%的正确率,上网搜索了一下,发现可能是 有些测试样例答案不唯一。

(我是在蓝桥杯官网写的题目,没有测试样例可以查看,所以也不确定)

(之后等我发现哪些样例答案不唯一的时候,再回来指出)

(敲代码时就有一种感觉,本题不应该只是让你(随随便便)将数据搬来搬去,要用到算法才正常)


前言(下):

参考文章:蓝桥杯 算法训练 娜神平衡_陆小路-1的博客-CSDN博客

然后找到 我觉得 是解这道题的文章 里面最好的一篇(上面这篇)。

有详细注释!!!(第一次看代码就懂怎么解题,少走了弯路)

强烈建议没有看过的同学先去看看。

接下来,我只着重讲一下"算法"方面的内容,即上面这篇文章中用到的 递归 是如何进行解题的。


开始:

我修改后的代码:(方便理解:去除了容器vector,减少了一些函数的参数<=>用全局变量代替)

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

//数量n,限制r
int n, r;

//左堆,左堆数量
int L[101] = { 0 }; int L_ = 0;
//右堆,右堆数量
int R[101] = { 0 }; int R_ = 0;

//原始数据
int date[101] = { 0 };
//标记对应数据状态:已选 或 未选
bool ind[101] = { 0 };

//打印数组 - 无回车
void print(int ii[], int n)
{
	for (int i = 1; i <= n; i++)
		cout << ii[i] << " ";
	return;
}

//递归调用
bool func(int L_R = 0)
{
	//差值(绝对值)
	int temp = L_R > 0 ? L_R : -L_R;

	//若绝对值超出,则结束(注意temp不要去绝对值啊)
	if (temp > r)
	{
		return false;
	}

	//当所有数据都能用上时 - 就代表可以结束了
	if (L_ + R_ == n)
	{
		//排序 - 升序
		sort(L + 1, L + L_);
		sort(R + 1, R + R_);

		//寻找第一个数据
		for (int i = 1; i <= L_; i++)
		{
			if (date[1] == L[i])
			{
				print(L, L_);
				cout << endl;
				print(R, R_);
				return true;
			}
		}
		print(R, R_);
		cout << endl;
		print(L, L_);
		return true;
	}

	int i;
	for (i = 1; i <= n; i++)
	{
		//检查状态:若没有使用(false)
		if (!ind[i])
		{
			//标记为已使用(true)
			ind[i] = true;

			//加给左堆
			L[++L_] = date[i];
			//选定之后,往后继续
			if (func(L_R + date[i]))
				break;

			//取出
			L[L_] = 0;
			L_--;

			//加给右堆
			R[++R_] = date[i];
			//选定之后,往后继续
			if (func(L_R - date[i]))
				break;

			//取出
			R[R_] = 0;
			R_--;

			//就当无事发生
			ind[i] = false;
		}
	}
	//若能走到这里,i能到n:返回1;i没到n,返回0
	//告诉上层的判断:该层及其以下 递归能不能遍历完 所有的元素
	return !(i == n + 1);
}


int main()
{
	cin >> n >> r;

	//输入数据
	for (int i = 1; i <= n; i++)
		cin >> date[i];

	//开始递归
	func();

	return 0;
}

以下是程序运行时的截图(运行时可以自己设置断点,定下标记来查看,不需要的自行跳过)

测试数据:n = 4 , r = 10 , date : 9 6 4 20

        初始左右两堆为0

        9放入左堆

        6放入左堆(但超出r,下一步更改选择)

         6放入右堆

        4放入左堆

         20放入左堆(超出r,下一步更改选择)

        20放入右堆(也超出r,下一步回溯

        4放入右堆

        20放入左堆(超出r,下一步更改选择)

         20放入右堆(也超出r,下一步回溯

 上一步4,这步跳过,直接20放入左堆(超出)

         20放入右堆(也超出r,下一步回溯

        注意:4回溯完以后,就到6了。并且6放右堆也做完了,所以这步是跳过6,再次4放左堆(但是超出r,下一步重选)

         4放入右堆

        注意:每次选数字都是从"左到右",选未被选过的数字,所以在4放入右堆之后,下一个选择的数字是6,6放入左堆(还是超出,下一步重选)

        6放入右堆

        20放入左堆(超出r,下一步更改选择)

        20放入右堆(也超出r,下一步回溯

 注意:这次回溯6,下一个到2020放入左堆

        一样,再20放入右堆

 这次4(左右堆都选了)这次只能再放弃,则到20注意:可能这里有同学有疑惑了,这次放弃了4,未选的不是有620吗?从左到右的话(9 6 4 20),不应该选6么?但请注意:从左到右是 基于 新的递归/当前位置 。意思是说,虽然是从左到右,但是每个递归函数内部是有一个for循环的,且控制到那个数字的i是++的,即4不选,下一个应该是4后面的20,不是6,除非又到的下一层递归,i重头开始……(不懂也没什么关系,继续往下)所以:20放入左堆

        20放入右堆

 9入左堆的所有情况都完了,现在该9放入右堆

        6放入左堆

        4放入左堆

        20放入左堆

        20放入右堆

         4放入右堆

        20放入左堆

        20放入右堆

        放弃4, 20放入左堆

        20放入右堆

        6放入右堆

        放弃6, 4放入左堆

        6放入左堆

        20放入左堆

        20放入右堆

        6放入右堆

        放弃620放入左堆

        20放入右堆

        4放入右堆

        放弃420放入左堆

        20放入右堆

        放弃96放入左堆

        9放入左堆

        9放入右堆

        4放入左堆

        20放入左堆

        20放入右堆

        4放入右堆

        20放入左堆

        20放入右堆

        放弃420放入左堆

        20放入右堆

         放弃94放入左堆

        9放入左堆

        9放入右堆

        20放入左堆

        20放入右堆

        放弃920放入左堆

        20放入右堆

        9放入左堆
呼~~~终于结束了,62张图啊。


总结起来就是:

每层递归函数都会进行一个操作(对一个数字放左堆/右堆放弃),并且该层递归会遍历所有数字。

值得注意的是:

每层递归函数内的循环都是从头开始,而保存原始数据/左右堆的数据又可以随时改变(去下层时可更新数据,返回上层递归时又会清除数据恢复到去下层递归之前的状态……)

然后:

若无中间符合条件导致递归终止,其会枚举(若干数字的)所有组合情况。

只不过加了一些判断条件可以完成题意。


结束:

嗯,不难,看过我之前的《无聊的逗》或者学过深搜算法的同学理解起来应该没问题。

个人感觉跟DFS没太大区别,核心思想差不多……

(唉~就应该用这样的思路去解题的,不然最高才80%)

希望理解吧。

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Lyz_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值