English: Consider a function which, for a given whole numbern, returns the number of ones required when writing out all numbers between 0 andn. For example, f(1) = 1,f(13) = 6. Notice that f(1) = 1. What is the next largestn such that f(n) = n? n <= 4 000 000 000.
e.g. f(n) = 6, because the number of "1" in 1,2,3,4,5,6,7,8,9,10,11,12,13 is 6 (1,10,11,12,13).
中文:考虑这样一个函数,对于一个给定的整数n,它能够返回罗列出0到n的所有数字时字符"1"出现的次数。比如f(1) = 1,f(13) = 6,并注意 f(1) = 1。对于n <= 4 000 000 000,满足 f(n) = n 的 n 的最大值是多少?
例:f(n) = 6,因为 0,1,2,3,4,5,6,7,8,9,10,11,12,13 中 "1" 的个数为 6 (1,10,11,12,13)。
解题思路:
为了计算 f(n) 的值,我们需要写一个函数求出从1到n之间"1"的个数。这与 POJ3286 -- How many 0's? 那道计算"0"的个数的题目类似,基本思路是分别计算1到n之间所有的数中"1"出现在个位、十位、百位……上的次数,然后将各结果相加,即得到了f(n)的值。
然而,知道怎么计算f(n)的值后,如果直接暴力搜索,将 n 从 4 000 000 000 起始,逐次减1,每次求出其f(n)的值,看其是否满足 f(n) = n 来求解最大的 n 值,这对于本题中设定的数据范围是不实际的,速度将会很慢。
因此,为了能够快速求出 f(n) = n 在给定范围内的最大解,我们需要设计方法替代每次减1求 f(n) 的简单搜索,以便加快 n 从 4 000 000 000 处开始,向下逼近最接近的解的速度。
易知 f(n) 是一个非减函数,因为若 m < n ,则1到 n 之间的数包含了1到 m 之间的数,所以1到 n 之间的数中出现"1"的次数一定不会小于1到 m 之间的数中"1"出现的次数,即 f(n) 必然不小于 f(m)。
一个事实是,对于给定一个数n0,求出其 f(n0),然后比较 n0 与 f(n0) 的大小。若f(n0) = n0,则直接求得答案;若 f(n0) > n0 ,则对于所有满足 n0 < n < f(n0) 的n,f(n) >= f(n0) > n,均不符合条件,于是可以直接测试n = f(n0) 时的f(n),而不必逐次加1递增到f(n0);若 f(n0) < n0,则对于所有满足 f(n0) < n < n0 的n,f(n) <= f(n0) < n,均不符合条件,于是可以直接测试n = f(n0)时的f(n),而不必逐次减1递减到f(n0)。
根据上述事实,得到求[a, b](其中a >= 0)之间满足f(n)=n的最大解的算法G(a, b):
1. 若a > b,无解,返回-1,算法结束;否则求f(b),比较f(b)与b的大小,转2。
2. 若 f(b) = b,则b即为所求的解,返回b,算法结束;否则转3。
3. 若 f(b) < b:若 f(b) >= a,则令b = f(b),将求解区间缩小为[a, b] = [a, f(b)],转3;若f(b) < a,则在[a, b]区间内无解,返回-1,算法结束。若f(b) > b 则转4。
4. 若 f(a) = a,则返回a与G(a+1, b)中的较大者,算法结束;否则转5。
5. 若 f(a) > a:若f(a) <= b,则令a= f(a),将求解区间缩小为[f(a), b],转5;若f(a) > b,则在[a, b]区间内无解,返回-1,算法结束。否则f(a) < a,转6。
6. 令 mid = (a+b)/2,将原区间分为两半。若G(mid, b)存在,则返回G(mid, b),算法结束;否则返回G(a, mid-1),算法结束。
面试题中a = 0, b = 4 000 000 000,求得的G(a, b)即为答案。
下面是实现该算法的C代码:
#include <stdio.h>
#include <stdlib.h>
//poj3286
__int64 arr10[] = {1, 10, 100, 1000, 10000, 100000,
1000000, 10000000, 100000000, 1000000000, 10000000000};
//计算0到n之间"1"的个数
__int64 getonesnum(__int64 n)
{
__int64 result = 0;
int bitnum;
if (n <= 0)
return 0;
// if (n < 10)
// return result;
bitnum = 0;
while (bitnum < 10 && n / arr10[bitnum])
{
if (n / arr10[bitnum] % 10 == 0)
{
result += (n / arr10[bitnum + 1]) * arr10[bitnum];
}
else if (n / arr10[bitnum] % 10 == 1)
{
result += (n / arr10[bitnum + 1]) * arr10[bitnum];
result += n % arr10[bitnum] + 1;
}
else if (n / arr10[bitnum] % 10 > 1)
{
result += (n / arr10[bitnum + 1] + 1) * arr10[bitnum];
}
++bitnum;
}
return result;
}
//用递归的方法求出low到high满足f(n)=n的最大解
__int64 getboundedans(__int64 low, __int64 high)
{
__int64 mid, ans = 0, f;
if (high < low)
return 0;
f = getonesnum(high);
while (high >= low && f < high)
{
high = f;
f = getonesnum(high);
}
if (high < low)
return 0;
else if (f == high) //若进入while循环,则此式理应成立
return high;
// f(high) > high 的情况
f = getonesnum(low);
while (low <= high && f > low)
{
low = f;
f = getonesnum(low);
}
if (low > high)
return 0;
else if (f == low)
{
ans = getboundedans(low + 1, high);
if (ans > low) // 此时(low + 1, high)区间有解
return ans;
else
return low;
}
// f(high) > high 且 f(low) < low 的情况
--high;
++low;
mid = (low + high) / 2;
ans = getboundedans(mid, high);
if (ans != 0)
return ans;
else
return getboundedans(low, mid - 1);
}
//找出不大于n的 f(n)=n 的最大解
__int64 findmaxsolution(__int64 n)
{
if (n < 1) // f(0) == 0 ?
return 0;
else
return getboundedans(1, n);
}
int main()
{
__int64 n;
//测试
n = findmaxsolution(400000);
printf("n = %I64d, f = %I64d\n", n, getonesnum(n));
n = findmaxsolution(4000000000);
printf("n = %I64d, f = %I64d\n", n, getonesnum(n));
n = findmaxsolution(400000000000);
printf("n = %I64d, f = %I64d\n", n, getonesnum(n));
return 0;
}