等比数列数码 4 计数题解(C++)

等比数列数码 4 计数

题目:

  考虑等比数列 1, 2, 4, … 。这数列的第一百万项是个 301030 位数,最左边的数码是 4 。问题是:这个数列的前一百万项中,有多少项的最左边的数码是 4 ?

题目来源:

  酒井算协 微信公众号:“沉思的图灵”活动

题解:

法一:暴力高精度 O ( n 2 ) O(n^2) O(n2)

  用高精度暴力解题无疑是最自然也最容易想到的方法,虽然效率不高,但在想不到更好的方法时这也不失为一种好方法。

思路:

  用高精度乘法(高精乘低精)计算出等比数列的每一项,同时判断最高位是否为 4,如果是 4 就累加。

时间复杂度:

  一共n项,就需要做n次高精度乘法,每次需要做的乘法次数为 2n-1 的位数,即 ⌊ n l g 2 ⌋ + 1 \lfloor nlg2 \rfloor+1 nlg2+1。故总共需要做 ∑ i = 1 n ⌊ i l g 2 ⌋ + 1 \sum\limits_{i=1}^n \lfloor ilg2\rfloor+1 i=1nilg2+1次运算。此可近似看做等差数列求和,故时间复杂度为 O ( n 2 ) O(n^2) O(n2)

代码:
#include <iostream>
using namespace std;
const int M = 301030 + 10;
const int N = 1e6;
int s[M];
int main() {
	int len, p, x, res = 0;
	s[1] = len = 1;
	for (int i = 2; i <= N; i++) {
		p = 1, x = 0; 		//指针p,进位x
		while (p <= len) {	//高精度计算
			s[p] = (s[p] << 1) + x;
			x = s[p] / 10;
			s[p] %= 10;
			p++;
		}
		if (x == 0)	p--;
		else {
			s[p] = x;
			len++;
		}
		if (s[p] == 4)	res++;	//判断最高位是否为4
	}
	cout << res << endl;
	return 0;
}

计算结果:96911

用时:760.8s(12分40秒)

法二:简单的数学运算 O ( n ) O(n) O(n)

  注:数学上, ⌊ x ⌋ \lfloor x \rfloor x表示x向下取整; ⌈ x ⌉ \lceil x \rceil x表示x向上取整; { x } \{x\} {x}表示取 x 的小数部分,即 { x } = x - ⌊ x ⌋ \{x\}=x-\lfloor x \rfloor {x}xx.

思路:

  1.首先我们可以先计算出 2 n 2^n 2n 的位数。 2 n − 1 2^{n-1} 2n1 的十进制表示有 m 位,则满足:
1 0 m − 1 ≤ 2 n < 1 0 m 10^{m-1}\le2^n<10^m 10m12n<10m
  同取常用对数,得:
m − 1 ≤ n l g 2 < m m-1\le nlg2<m m1nlg2<m
  m为正整数,故 m = ⌊ n l g 2 ⌋ + 1 m=\lfloor nlg2\rfloor+1 mnlg2+1.

  2.判断最高位是否为 4

   2 n 2^n 2n的最高位为4的充要条件是:
4 × 1 0 m − 1 ≤ 2 n < 5 × 1 0 m − 1 4×10^{m-1}\le 2^n<5×10^{m-1} 4×10m12n<5×10m1
  同取常用对数,得:
m − 1 + 2 l g 2 ≤ n l g 2 < m − 1 + l g 5 m-1+2lg2\le nlg2<m-1+lg5 m1+2lg2nlg2<m1+lg5
  即:
⌊ n l g 2 ⌋ + 2 l g 2 ≤ n l g 2 < ⌊ n l g 2 ⌋ + l g 5 \lfloor nlg2\rfloor+2lg2\le nlg2<\lfloor nlg2\rfloor+lg5 nlg2+2lg2nlg2<nlg2+lg5

2 l g 2 ≤ { n l g 2 } < l g 5 2lg2\le \lbrace nlg2 \rbrace <lg5 2lg2{nlg2}<lg5

  经过简单的计算,我们可以得出结论:要判断 2 n 2^n 2n最高位是否为 4,只需判断不等式 2 l g 2 ≤ n l g 2 < l g 5 2lg2\le {n lg2}<lg5 2lg2nlg2lg5是否成立。

  于是通过依次判断 n 从0到 1 0 6 - 1 10^6-1 1061变化时不等式是否成立,累加成立的次数,即可得出答案。

时间复杂度:

  对于每个 n 我们都要进行一次判断,故时间复杂度为 O ( n ) O(n) O(n).

代码:
#include <iostream>
#include <cmath>
using namespace std;
const int N = 1e6;
const double minn = 0.60205999133;	//2lg2
const double maxx = 0.69897000434;	//lg5
const double k = 0.30102999566;	//lg2
double f(double x) {	//返回浮点数的小数部分
	return x - floor(x);
}
int main() {
	int i, res = 1;	//初始直接算上4
	for (i = 3; i < N; i++) {
		double cmp = f(i * k);
		if (cmp >= minn && cmp < maxx)	res++;
	}
	cout << res << endl;
	return 0;
}

法三:对法二再优化 O ( n ) O(n) O(n)

思路:

  法二我们通过枚举 n 来逐个判断,现在我们换个角度,也可以尝试枚举十进制数的位数来解题。换句话说,统计每个区间 [ 1 0 i , 1 0 i + 1 ) [10^i,10^{i+1}) [10i10i+1) 中2的整次幂的个数并累加,其中 0 ≤ i < 301030 0\le i<301030 0i301030,且 i 为整数。

  依然需要一点点数学运算:

  由法二同理可得:
i + 2 l g 2 ≤ k l g 2 < i + l g 5 i+2lg2\le klg2<i+lg5 i+2lg2klg2i+lg5
  同除以 l g 2 lg2 lg2,得:
2 + i l o g 2 10 ≤ k < l o g 2 5 + i l o g 2 10 2+ilog_210\le k<log_25+ilog_210 2+ilog210klog25+ilog210
  满足上不等式成立的整数k的个数,即区间 [ 2 + i l o g 2 10 , l o g 2 5 + i l o g 2 10 ) [2+ilog_210,log_25+ilog_210) [2+ilog210log25+ilog210)​ 中的整数个数,即:
⌊ l o g 2 5 + i l o g 2 10 ⌋ - ⌈ 2 + i l o g 2 10 ⌉ + 1 \lfloor log_25+ilog_210\rfloor-\lceil 2+ilog_210\rceil + 1 log25+ilog2102+ilog210+1
  值得注意的是:以上说法有一点不严谨。因为我们计算的实际是所有位数小于等于301030的 2 的整次幂中最高位为 4 的个数,而我们不应考虑大于 1 0 6 10^6 106且位数恰好为301030的2的整次幂。不过经计算发现,恰好不存在这样的数字,因此我们也不需再特殊处理了。

时间复杂度:

  显然相比法二,我们从枚举一百万个 n 优化到了只需枚举三十多万个 i ,确实效率高了。但是其实我们需要做的运算次数是 ⌊ n l g 2 ⌋ + 1 \lfloor nlg2\rfloor+1 nlg2+1,时间复杂度依然是线性的不变,为 O ( n ) O(n) O(n).

代码:
#include <iostream>
#include <cmath>
using namespace std;
const int M = 301030;
const double a = 2.32192809489;	//log2 5
const double b = 3.32192809489;	//log2 10
int main() {
	int i, res = 0;
	for (i = 0; i < M; i++) 
		res += floor(a + i * b) - ceil(2 + i * b) + 1;
	cout << res << endl;
	return 0;
}

计算结果:96911

用时:0.03080s

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值