清华大学考研复试机试:函数求值

博客围绕给定正整数N,求小于等于N的自然数中1和2的个数之和F(N)展开。介绍了两种求解方法,重点讲解通用公式法,以F(123)到F(1234)为例,详细推导结果公式,还指出两种方法在牛客网都不能AC。

题目描述

给定正整数N,函数F(N)表示小于等于N的自然数中1和2的个数之和,例如:1,2,3,4,5,6,7,8,9,10序列中1和2的个数之和为3,因此 F(10)=3。输入N,求F(N)的值,1=<N<=10^100(10的100次方)若F(N)很大,则求F(N)mod20123的值

示例:

输入

10
10

输出

3
3

分析

这道题是一道规律题,难度比较大。大概有两种方法可以求解,一种是参考编程之美这本书,其中有一节是1的个数,就是用来求解这道题。将书中的方法分别用来求解1和2的个数即可,这种方法这里不会讲到,感兴趣的读者可以直接看书。另一种方法便是找到一个通用的公式来求解,因为这种方法比起上一种方法更容易理解,所以这里讲解这种方法。

首先讲一下这种方法的大致过程,比如要求F(12345),依次求解F(1),F(12),F(123),F(1234),F(12345)。因为在求解过程中,我们需要利用前面一步求出的结果,这里假设我们已知F(123) = result,那么F(1234)的求解过程如下所示。

可以理解为从123,求解123X的过程,X为任意数。设count为123这个数中1和2的个数,显然count = 2。我们要求的数大概是之前已经求出的数的10倍,那么是不是之前包含1和2的数的个数也大概翻了10倍呢?比如11包含2个1,那么110,111,…,119中包含了20个1,注意这里没有算个位数的1和2,一定要理解这个关键所在,这和后面的式子密切相关,如果看到后面不理解的话,再回过头来好好理解这句话。这样我们就能根据前面所求出的结果,计算出现在的一部分值。为什么说是一部分值呢?因为乘以10的过程中不能包含所求数中1和2的个数,即第一个部分为:(result - count) * 10。count前面已经说明,这里的值是2。因为这里要求的数时1234,那么以123开头的数就只有5个,当然不能直接乘以10了。第一步计算到这里就结束了,在这一步中我们已经计算出了结果的大部分数,剩下的工作就是把遗漏的数给加回来,就能计算出最终结果了。在这里我们回顾一下还有哪些数没有计算在内,首先是以上一次数开头的数,这里是以123开头的数没有计算。其次是所有数的个位数,在乘以10的过程中,只计算了前面高位的数,并没有加上个位数,比如上面提到过的以11开头的数,在计算111这个数时,只算了前两位的值为2,并没有加上个位的1。

接下来开始计算个位数中1和2的个数,这个比较简单,因为每10个数中就又两个1和2,那么从0到1234之间间隔有多少个10呢?答案是123个,因此第二部分值为num*2,其中num = 123。这里要理解到之前算第一部分值的时候并没有算10以下的数,因为每个数都乘以了10。比如2中有1个2,那么20,21,…,29中的第一部分值为10,所以计算时并没有包含2这个数。因此第二部分值要从0开始算起,中间间隔了多少个10。这样第二部分的值就计算结束了,最后只剩以123开头的数没有计算了。

以123开头的数也要分为两部分进行计算,高位部分和个位部分。其中高位部分是count * (temp + 1),count前面已经提到过,temp是个位数的值。比如1234,count是123中1和2的个数,为2;temp是个位数的值,为4。这个想必大家都清楚其含义,就不再赘述。而个位部分为min(temp,2),temp和上面的含义相同。个位的值最多就为2,因为10以内的数只有1和2两个数包含题目中所求的数。

这样所有的数都计算完毕,结果公式为result = (result - count) x 10 + num x 2 + count x (temp+1) + min(temp, 2)。其中result为上一次的计算结果,count为上一个计算的数中1和2的个数,num为上一次计算的数,temp为这一次计算的数的个位数的值。

总结一下这道题的计算思路,以F(123)到F(1234)为例:首先分为两大部分,123X和122X及其一下的数。这两大部分又分别分为两个小部分,高位数和个位数。所以公式中总共有四个部分。

还有一点需要注意,不管是编程之美上的方法,还有上面提到的方法,在牛客网中都不能AC。牛客网上这道题的通过率为0,emmm,不知道为什么。

代码如下:

#include<iostream>
#include<string>
#include<algorithm>

using namespace std;

int main(void)
{
	string str;
	while(cin >> str)
	{
		int length = str.length();
		//result为结果,count为上一个数1和2的个数,num为上一个数
		int result = 0, count = 0, num = 0;
		for(int i = 0; i < length; ++i)
		{
			int temp = str[i] - '0';//个位数的值
			result = ((result - count)*10 + num*2 + count*(temp+1) + min(temp, 2)) % 20123;
			num = (num*10 + temp) % 20123;
			if(temp == 1 || temp == 2)
			{
				++count;
			}
		}
		cout << result << endl;
	}
	return 0;
}
### 清华大学成绩排序算法实现方法 对于清华大学考研复试中的成绩排序问题,具体的要求是在给定一组学生及其对应的学号和成绩之后,按照成绩从小到大对学生信息进行排序;当遇到多名学生的成绩相同时,则依据这些学生的学号由小至大来决定先后次序[^1]。 针对这一需求,可以采用C++编程语言结合`<algorithm>`库内的`sort()`函数完成此任务。为了满足题目关于稳定性的隐含要求——即保持原序列中相同元素之间的相对位置不变,可以在定义比较器时引入额外条件判断,确保即使在成绩相同的前提下也能通过学号维持原有的输入顺序[^2]。 下面是具体的代码示例: ```cpp #include<iostream> #include<algorithm> using namespace std; struct Student { int id; int grade; }; // 自定义比较函数用于std::sort() bool compare(const Student &a, const Student &b) { // 当成绩不同,按成绩升序排列; // 若成绩相同,则按id升序排列。 return (a.grade != b.grade) ? a.grade < b.grade : a.id < b.id; } int main() { int numStudents; cin >> numStudents; Student students[numStudents]; for (int i = 0; i < numStudents; ++i) { cin >> students[i].id >> students[i].grade; } // 使用自定义的比较函数对数组进行排序 sort(students, students + numStudents, compare); // 打印排序后的结果 for (const auto& s : students) { cout << s.id << " " << s.grade << endl; } } ``` 该程序首先读取输入的学生数量以及每位同学的成绩与编号,接着利用`sort()`配合自定义的比较逻辑对学生数据进行了处理并最终输出了已排序的结果列表[^5]。 #### 关键点解析 - **稳定性**:由于标准模板库(STL)`sort()`默认是非稳定的快速排序,因此需要特别注意如何设计比较规则以保证特定条件下(如同分情况下)的数据稳定性。这里通过对成绩优先级较低者先行考虑的方式间接实现了这一点。 - **效率考量**:虽然本题规模不大(`n≤100`),但依然推荐使用高效的内置排序功能而非手动编写低效的选择或冒泡等基本排序算法[^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值