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

版权声明:本文为博主原创,未经博主允许不得转载。转载请附上原文链接。 https://blog.csdn.net/qq_38341682/article/details/88647619

题目描述

给定正整数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;
}

猜你喜欢

转载自blog.csdn.net/qq_38341682/article/details/88647619