用c++求解一道简单的数学题

19 篇文章 1 订阅

最近在参加哆嗒数学网组织的闯关活动,第4关的题目为:

用0到9组成一个无重复位的数字,要求结果不能是2、3或者5的倍数,即因子中不能含有2、3、5中的任何一个.

求出满足条件的最大数字.

今天闲来无事,用程序解决了这个问题.

基本思路为,首先考虑十个数码全用的情况,此时各位的和为45,则此时无论怎么排列,结果都能被3整除,不符合题意.

所以程序的任务是从这n=10个digit中删掉尽可能少的数字,使总和不能被3整除,然后把这些数位进行排列,把得到的结果从大到小排列,然后找出其中第一个满足因子不含2或5这个条件的数字即可.具体代码为

#include<cassert>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
struct Combination {
	const int m; //解的位数

	vector<int> answer; //目前的答案,是一个长度为m的升序序列

	const int max_value;  //可能的最大值

	Combination(const vector<int>& values, int m)
		: m(m)
		, answer(m)
		, max_value(values.back())
	{
		//把s中的前m个元素复制到answer中,作为初始解
		for (int i = 0; i < m; ++i) {
			int x = values[i];
			answer[i] = values[i];
		}
	}

	void print()const {
		for (auto e : answer)
			cout << e << " ";
		cout << endl;
	}

	//把第index的值+1,右边各位等于左边的+1
	void inc(int index) {
		int x = answer[index];
		for (int i = index; i < m; ++i)
			answer[i] = ++x;
		assert(answer.back() <= max_value);
	}

	bool next()  //输出下一个组合,如果已经到头,则返回false
	{
		/*从右边开始找到第一个可以加1的位,即本位+1之后,这个值到最大值的距离
		>=右边的位数
		*/
		for (int i = m - 1, j = 0; i >= 0; --i, ++j) {
			int x = answer[i];
			if (max_value - x > j)   //本位+1之后,右边的j位仍然有可能组成一个升序序列
			{
				/*新的子序列为
				x+1,x+2,x+(j+1)<=max
				*/
				//从本位开始构造一个连续的升序子序列
				inc(i);

				return true;
			}
		}

		return false;
	}
};

//把一个数字组成值
int64_t get_value(const vector<int>& v) {
	int64_t p = 1;
	int64_t sum = 0;
	for (size_t i = 0; i < v.size(); ++i, p *= 10) {
		sum += (v[i] * p);
	}
	return sum;
}

int main() {
	vector<int> values(10);
	set<int> s;
	for (int i = 0; i < 10; ++i) {
		values[i] = i;
		s.insert(i);
	}

	for (int m = 1; m <= 9; ++m) {
		//从这10个数中扣掉m个数
		Combination c1(values, m);
		set<int64_t> answers;
		do {
			//计算扣掉以后的和
			int sum = 45;
			set<int> s1 = s;
			for (auto e : c1.answer) {
				sum -= e;
				s1.erase(e);
			}
			if (sum % 3 != 0) {
				//一个数能被3整除的条件是各位之和能被3整除
				//不能被3整除时,遍历n-m个数的所有排列
				vector<int> v;
				for (auto e : s1)
					v.push_back(e);

				//按升序遍历当前组合的所有可能排列
				do {
					int x = v[0];
					if (x % 2 && x % 5)  //判断当前解是否合理
					{
						int64_t y = get_value(v);
						answers.insert(y);
						/*注意此时得到的
						不一定是全局最优解,只是当前数码组合下的最大解.
						*/
						break;  //
					}
				} while (next_permutation(v.begin(), v.end()));
			}
		} while (c1.next());

		if (!answers.empty()) {
			cout << (*(answers.rbegin())) << endl;
			break;
		}
	}
	return 0;
}

输出结果为

987654301

容易验证它对3的余数为1,符合要求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值