336 lines
9.1 KiB
Markdown
336 lines
9.1 KiB
Markdown
|
||
## 对数知识推导及应用
|
||
|
||
### 一、对数知识
|
||
|
||
#### 1、对数概念
|
||
如果$\large N=a^x$,那么数$x$称为以$a$为底$N$的对数。记做:
|
||
$$\large x=log_aN$$
|
||
|
||
其中,$a$称做底数,$N$称做真数。需要注意的是底数$a$的限制条件:$a>0,a \neq 1$。
|
||
|
||
#### 2、对数与指数关系
|
||
<font color='blue' size=4><b>对数与指数互为逆运算:</b></font>
|
||
* 指数运算: 求$10$的$2$次方,即:$\large 10^2=100$
|
||
* 对数运算:求$10$的多少次方等于$100$,即:$\large log_{10}100=2$
|
||
|
||
|
||
#### 3、对数形式
|
||
* 常用对数:以$10$为底的对数记做:$\large log_{10}N=lgN$
|
||
* 自然对数:以无理数$e=2.71828...$为底数的对数简记为:$lnN$
|
||
[自然底数$e$的含义](https://www.toutiao.com/article/6986077169411703310)
|
||
|
||
* 一般对数:$log_aN$
|
||
|
||
#### 4、对数性质
|
||
|
||
<font color='blue' size=4><b>$1$的对数</b></font>
|
||
$1$的对数是$0$:
|
||
<font color='red' size=4><b>$$\large log_a1=0$$</b></font>
|
||
|
||
<font color='blue' size=4><b>积的对数</b></font>
|
||
<font color='red' size=4><b>$$\large log_aMN=log_aM+log_aN$$</b></font>
|
||
|
||
证明:设$\large log_aM=p,log_aN=q$
|
||
则$\large a^p=M,a^q=N$,代入$\large log_aMN$,
|
||
得$\large log_aMN=log_a({a^p\cdot a^q})=log_a{a^{p+q}}=p+q=log_aM+log_aN$
|
||
|
||
**证毕**
|
||
|
||
---
|
||
<font color='blue' size=4><b>商的对数</b></font>
|
||
<font color='red' size=4><b>$$\large log_a{\frac{M}{N}}=log_aM-log_aN$$</b></font>
|
||
证明:设$\large log_aM=p,log_aN=q$
|
||
则$\large a^p=M,a^q=N$,代入$\large \displaystyle log_a{\frac{M}{N}}$,
|
||
得$\large \displaystyle log_a{\frac{M}{N}}=log_a{(\frac{a^p}{a^q})}=log_a{a^{p-q}}=p-q=log_aM-log_aN$
|
||
|
||
**证毕**
|
||
|
||
---
|
||
<font color='blue' size=4><b>幂的对数</b></font>
|
||
<font color='red' size=4><b>$$\large log_aM^b=b\cdot log_aM$$</b></font>
|
||
证明:设$\large log_aM=p$,则$\large a^p=M$
|
||
则$\large a^p=M$,代入 $\large log_aM^b$
|
||
得$$\large log_aM^b=log_a{(a^p)^b}=log_a{a^{p\cdot b}}=pb=b\cdot log_aM$$
|
||
|
||
**证毕**
|
||
|
||
---
|
||
<font color='blue' size=4><b>对数恒等式</b></font>
|
||
<font color='red' size=4><b>$$\large a^{log_aM}=M$$</b></font>
|
||
证明: 设$\large \displaystyle log_aM=p$,则$\large \displaystyle a^p=M$,代入$\large \displaystyle a^{log_aM}$
|
||
得$\large \displaystyle a^{log_aM}=a^{log_aa^p}=a^p=M$
|
||
|
||
**证毕**
|
||
|
||
---
|
||
|
||
#### 5、换底公式
|
||
<font color='red' size=4><b>
|
||
$$\large log_bN=\frac{log_aN}{log_ab}$$
|
||
</b></font>
|
||
|
||
证明:
|
||
设$\large log_bN=x$,则$\large b^x=N$
|
||
两边同时取以$a$为底的对数
|
||
$$\large log_ab^x=log_aN \\
|
||
x\cdot log_ab=log_aN \\
|
||
x=\frac{log_aN}{log_ab} \\
|
||
即log_bN=\frac{log_aN}{log_ab}
|
||
$$
|
||
|
||
---
|
||
|
||
#### 6、其他公式
|
||
<font color='red' size=4><b>$$\large log_{a^n}a=\frac{1}{n}$$</b></font>
|
||
证明:
|
||
设$\large log_{a^n}a=p$
|
||
则$\large (a^n)^p=a$,即$\large a^{np}=a$
|
||
两边同时取以$10$为底对数,$$\large lga^{np}=lga \\
|
||
\Rightarrow np \cdot lga=lga \\
|
||
\Rightarrow
|
||
np=1 \\
|
||
\Rightarrow p=\frac{1}{n}
|
||
$$
|
||
**证毕**
|
||
|
||
---
|
||
<font color='red' size=4><b>$$\large \frac{1}{log_ab}=log_ba $$</b></font>
|
||
证明:
|
||
用换底公式:
|
||
$$\large \frac{1}{log_ab}=\frac{1}{\frac{lgb}{lba}}=\frac{lga}{lgb}=log_ba$$
|
||
|
||
**证毕**
|
||
|
||
---
|
||
<font color='red' size=4><b>$$\large log_{a^n}M=\frac{1}{n}\cdot log_aM $$</b></font>
|
||
|
||
证明:设$\large log_aM=p$
|
||
则$\large a^p=M$,代入$\large log_{a^n}M$
|
||
$$\large log_{a^n}a^p=p\cdot log_{a^n}a=\frac{1}{n}\cdot p=\frac{1}{n}\cdot log_aM$$
|
||
**证毕**
|
||
|
||
---
|
||
|
||
### 二、典型应用
|
||
|
||
#### 1、求数$a$的位数
|
||
~~暴力解法~~
|
||
```c++
|
||
#include <stdio.h>
|
||
int a,b;
|
||
int main(){
|
||
scanf("%d",&a);
|
||
while(a){
|
||
a=a/10;
|
||
b++;
|
||
}
|
||
printf("%d ",b);
|
||
return 0;
|
||
}
|
||
```
|
||
可以吗?可以,就是如果算的数太多,而且每个数很大,就太慢了,人总是贪婪的,想要更快的办法:
|
||
|
||
<font color='blue' size=4><b>公式</b></font>
|
||
<font color='red' size=4><b>
|
||
$$\large a的位数 = log_{10}(a) + 1$$
|
||
</b></font>
|
||
|
||
证明:
|
||
|
||
假设 $10^{x-1} <= a < 10^{x}$,(想一想 $100$,$999$和$1000$) ,可见$a$的位数为 $x$ 位。
|
||
|
||
我们再对上式用 $log_{10}$
|
||
|
||
$$\large log_{10}10^{x-1}<=log_{10}a<log_{10}10^x$$
|
||
|
||
得 $$\large x-1 <= log_{10}a < x$$
|
||
|
||
则 $$\large x-1 = log_{10}a \\
|
||
\Rightarrow \\
|
||
x = log_{10}a + 1$$
|
||
|
||
所以:$a$的位数为 $$\large log_{10}a + 1$$
|
||
|
||
**证毕**
|
||
|
||
$C++$语言中只有$log$(以$e$为底)和$log10$(以$10$为底)两种函数。
|
||
|
||
如果想表达$log_ab$ 那么可以使用换底公式:$log(b)/log(a)$来解决。
|
||
```c++
|
||
#include<iostream>
|
||
#include<cmath>
|
||
using namespace std;
|
||
int main(){
|
||
int n;
|
||
cin>>n;
|
||
cout<<(int)(log10(n))+1;
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 2、求数$n!$的位数
|
||
|
||
**1、递推法**
|
||
先利用一下求数字$a$的位数公式:
|
||
|
||
$$\large n!的位数=log_{10}(n!)+1$$
|
||
|
||
展开一下阶乘的概念
|
||
|
||
$$\large =log_{10}(n\times(n-1)\times(n-2)\times...\times1)+1$$
|
||
|
||
利用积的对数性质
|
||
|
||
$$\large =log_{10}(n)+log_{10}(n-1)+…+log_{10}(1)+1$$
|
||
|
||
编程时,利用这个递推的关系**由小到大**进行递推即可。
|
||
|
||
#### 练习题
|
||
[$POJ$ $1423$](http://poj.org/problem?id=1423)
|
||
```c++
|
||
#include <iostream>
|
||
#include <cstdio>
|
||
#include <cmath>
|
||
|
||
using namespace std;
|
||
/*
|
||
思想:底数相同的两个数相乘底数不变,指数相加。
|
||
通过log10(x)得到指数,依次相加即可,即可以得到阶乘的位数
|
||
*/
|
||
const int N = 10000010;
|
||
int a[N];
|
||
|
||
int main() {
|
||
//预处理出阶乘的位数
|
||
double x = 0;
|
||
for (int i = 1; i < N; i++) {
|
||
x += log10((double)i); // POJ对于log10(i)会报编译错误,需要转为double
|
||
a[i] = x + 1;
|
||
}
|
||
|
||
// T次询问
|
||
int T;
|
||
scanf("%d", &T);
|
||
while (T--) {
|
||
int n;
|
||
scanf("%d", &n);
|
||
printf("%d\n", a[n]);
|
||
}
|
||
return 0;
|
||
}
|
||
```
|
||
这样的做法时间复杂度为 $O(n)$ ,当$n$的范围特别大时就不好用了,下面我们来找到$O(1)$的做法 <font color='red' size=5><b>斯特林公式</b></font> :
|
||
|
||
**2、斯特林($Stirling$)公式**
|
||
斯特林公式 : 是一条用来 <font color='blue' size=5><b>计算$n$的阶乘近似值</b></font> 的数学公式。一般来说,当$n$很大的时候,$n$阶乘的计算量十分大,所以斯特林公式十分好用,即使在$n$很小的时候,斯特林公式的取值也十分准确。
|
||
|
||
$$\LARGE n!=\sqrt{2\pi n}(\frac{n}{e})^n$$
|
||
|
||
* 用斯特林公式求$n!$
|
||
* 再用求$a$的位数公式计算$n!$的位数:$$\displaystyle \LARGE log_{10}( \sqrt{2\pi n} *(\frac{n}{e})^n)+1$$
|
||
|
||
变形一下:
|
||
<font color='red' size=4><b>
|
||
$$\LARGE log_{10}(2\pi n)/2+nlog_{10}(n/e)+1$$</b>
|
||
</font>
|
||
|
||
#### 练习题
|
||
[$POJ$ $1423$](http://poj.org/problem?id=1423)
|
||
```c++
|
||
#include <iostream>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <vector>
|
||
#include <map>
|
||
#include <queue>
|
||
#include <algorithm>
|
||
#include <math.h>
|
||
#include <cstdio>
|
||
|
||
#define PI acos(-1)
|
||
#define E exp(1.0)
|
||
|
||
int main() {
|
||
int T;
|
||
scanf("%d", &T);
|
||
while (T--) {
|
||
int n;
|
||
scanf("%d", &n);
|
||
int y = log10(2 * PI * n) / 2 + log10(n / E) * n + 1;
|
||
printf("%d\n", y);
|
||
}
|
||
}
|
||
```
|
||
* 主要是利用利用数学函数中的反三角函数,但是要注意一定引入$math$包$$\LARGE acos(-1)=\pi$$
|
||
|
||
|
||
|
||
#### 3、求$2^p-1$的位数
|
||
首先我们知道$\large 2^p-1$与$\large 2^p$有着相同的位数,因为$2$的次方满足了<font color='blue' size=4><b>最后一位不为零</b></font>的要求,所以<font color='red' size=4><b>减一后位数并不会改变</b></font>,那么我们可以直接求$\large 2^p$的位数。
|
||
|
||
<font color='blue' size=4><b>使用的知识是:求数字$a$的位数办法:</b></font>
|
||
|
||
<font color='red' size=4><b>$$\LARGE 2^p位数=log_{10}2^p+1= p\cdot log_{10}{2}+1$$</b></font>。
|
||
|
||
练习题:
|
||
[P1045 [NOIP2003 普及组] 麦森数](https://www.luogu.com.cn/problem/P1045)
|
||
|
||
```c++
|
||
#include <bits/stdc++.h>
|
||
|
||
using namespace std;
|
||
const int N = 1e5 + 10;
|
||
int a[N], al;
|
||
int b[N], bl;
|
||
|
||
void mul(int a[], int &al, int b[], int bl) {
|
||
int c[N] = {0}, cl = al + bl;
|
||
for (int i = 1; i <= al; i++)
|
||
for (int j = 1; j <= bl; j++)
|
||
c[i + j - 1] += a[i] * b[j];
|
||
|
||
int t = 0;
|
||
for (int i = 1; i <= al + bl; i++) {
|
||
t += c[i];
|
||
c[i] = t % 10;
|
||
t /= 10;
|
||
}
|
||
memcpy(a, c, sizeof c);
|
||
al = min(500, cl);
|
||
//前导0
|
||
while (al > 1 && a[al] == 0) al--;
|
||
}
|
||
|
||
//快速幂+高精度 x^k
|
||
void qmi(int x, int k) {
|
||
a[++al] = 1, b[++bl] = x; // 2 ^100 b[1]=2
|
||
while (k) {
|
||
if (k & 1) mul(a, al, b, bl);
|
||
k >>= 1;
|
||
mul(b, bl, b, bl);
|
||
}
|
||
}
|
||
|
||
int main() {
|
||
//计算 2^p-1的值
|
||
int y;
|
||
cin >> p;
|
||
//利用快速幂,计算2^p
|
||
qmi(2, p);
|
||
//最后一位减去一个1,因为2^k最后一位肯定不是0,所以减1不会产生借位,直接减去即可!
|
||
a[1]--;
|
||
|
||
//一共多少位
|
||
printf("%d\n", (int)(p * log10(2) + 1));
|
||
|
||
for (int i = 500; i; i--) {
|
||
printf("%d", a[i]);
|
||
//该换行了,就是到了第二行的行首
|
||
if ((i - 1) % 50 == 0) puts("");
|
||
}
|
||
return 0;
|
||
}
|
||
``` |