数论

## 模运算

$$
( a + b ) \% p = ( a \% p + b \% p ) \% p
$$

$$
( a * b ) \% p = ( (a \% p ) * (b \% p ) ) \% p
$$

$$
c * ( a \% p ) = ( c *a ) \% ( c *p )
$$


## 欧几里得算法

对于任意两个正整数 a,b ,都有:
$$
a=kb+r (k,r∈N)
$$

所以有:
$$r=a%b$$

然后我们假设 c 是 a 和 b 的最大公约数,即
$$c=gcd(a,b)$$

然后,我们就能得到:
$$ c|a 和c|b (x|y 表示 x 能够整除 y , y能被x整除 , 也就是y/x是整数)$$


然后又因为上面那个式子,有:
$$r=a−kb$$
$$c|r$$
$$c=gcd(b,r)$$
即
$$gcd(a,b)=gcd(b,a\%b) $$

而且
$$gcd(a,0) = a$$


辗转相除法函数代码:

```cpp
int gcd(int a,int b)//就是欧几里得算法函数,即辗转相除法,求gcd(a,b)
{
int c;
while(b!=0)
{
c=a;
a=b;
b=c%b;
}
int ans=a;
return ans;
}
```

 

## 扩欧算法
![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/fa0238aaed45b43410c8e3af8133328e.png)
![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/216303c11826f06564ae2dc2a9ef18d3.png)

**例题1**
求关于$x$的同余方程$a x \equiv 1 \pmod {b}$ 的最小正整数解.**输入数据保证一定有解**

## 乘法逆元
![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/3222b7262bdb26957ee4f94ccd546462.png)

**算逆元的三个方法:**

第一个方法:
![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/e3a3e65165d01935b53c72fd123a30a1.png)

```cpp
inline void exgcd(LL a,LL b)//扩展欧几里得算法求乘法逆元
{
if(b==0)
{
x=1,y=0;
return ;
}
exgcd(b,a%b);
LL k;
k=x;
x=y;
y=k-(a/b)*y;
}
```

.
.
.
.


**第二个方法:**
![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/5bfb3ec99adebf8eddad4ffc023ccb49.png)

```cpp
int quick(int x,int p)//快速幂求乘法逆元,谨记,p是一个素数
{
int ans=1;
int d=p-2;
while(d)
{
if(d%2==1)
{
ans*=x;
ans%=p;
}
x*=x;
x%=p;
d/=2;
}
return ans;
}
```

.
.
.
.
**第三个方法:**
![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/0a5788bef7b456045827ee17bb9e1a64.png)


这里有一个模板题目[ 题目传送门](https://www.luogu.org/problemnew/show/P3811)

```cpp
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long x,y,n,f[3000010];
void work(long long n,long long p)//线性求逆元,时间复杂度O(n)
{
f[1]=1;
for(long long i=2;i<=n;i++)
{
f[i]=-(p/i)*f[p%i];
f[i]=(f[i]%p+p)%p;
}
}
int main()
{
long long a,p,b,n,i;
cin>>n>>p;
work(n,p);
for(long long i=1;i<=n;i++)
{
printf("%lld\n",f[i]);//处理出 最小正整数!!
}

return 0;
}
```

## 欧拉函数
对于正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目,记作φ(n).

注意,欧拉函数是一个**积性函数**,只要M,N互质,就可以直接φ(MN)=φ(M)*φ(N),在线性求1~N的欧拉函数数值的时候很有用。
另外:若a,b互质且f(ab)=f(a)*f(b),则f(x)是**积性函数**
若a,b不互质且也有f(ab)=f(a)*f(b),则f(x)是**完全积性函数**


![在这里插入图片描述](https://img-blog.csdnimg.cn/26af53c0317e4c70a0a85834ae845e04.png)

```csharp
#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;

LL n;
LL phi(LL x)
{
LL ans = x;
for(int i=2;i*i<=x;i++)
{
if( x%i==0 )
{
ans = ans / i * (i-1);
while( x%i==0 ) x/=i;
}
}
if( x>1 ) ans = ans / x * (x-1);
return ans;
}

int main()
{
LL x;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
printf("%lld\n",phi(x));
}
return 0;
}
```
考虑如何用类似于筛素数的方法筛出欧拉函数。
类似于素数,可以做出如下分类讨论:(设p为素数)
①$Phi(p)=p-1$
②$若已知Phi(x),pr_j能整除x: Phi(x*)=Phi(x)*p$
③$若已知Phi(x),且p不能整除x:Phi( x* pr_j )=Phi(x)*(pr_j-1)$
简单地证明上述式子:对于①,由于$p$本身是一个素数,那么比它小的所有数都和它互质,因此答案为$p-1$。对于②,$p$已经是x的质因子,因此我们看看欧拉函数的定义式可以的出括号相乘部分不会改变,只会把最前面的$x$变成$x*p$。对于③$p$是新加入的质因子,相当于式子前面多乘了个p,然后多加了一个括号,所以相当于原式多乘了: $p*(1-1/p)即(p-1)$。

```csharp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define N 1000100
using namespace std;

LL phi[N],n,pr[N],tot = 0,ans = 0;
bool pd[N];
void getphi()
{
phi[1] = 1;
for(int i=2;i<=n;i++)
{
if( pd[i]==false )
{
pr[++tot] = i;
phi[ i ] = i-1;
}
for(int j=1;j<=tot && pr[j]*i<=n;j++)
{
LL t = i*pr[j];
pd[ t ] = true;
if( i%pr[j] == 0 )
{
phi[ t ] = phi[i] * pr[j];
break;
}
else phi[ t ] = phi[i] * (pr[j] - 1);
}
}
}
int main()
{
memset(pd,false,sizeof(pd));
cin>>n;
getphi();
for(int i=1;i<=n;i++)
{
ans += phi[i];
}
cout<<ans;
return 0;
}
```

 

## 数论分块
在介绍整除分块之前,我们先来看一道算数题:已知正整数n,求
$$
\sum_{i=1}^n \lfloor \frac{n}{i} \rfloor
$$
我们写一个表格看一看1-20的整除是什么样子的
![在这里插入图片描述](https://img-blog.csdnimg.cn/2003f4bb71b246e8a4ed62e1018165e0.png)
表中同样的值会连续出现,而相同的值所划分的区间积是整出分块。整除的性质使得从1到n的数组表可根据数值划分为不同的分块,且分块数远远小于n。利用这种性质,我们如果能推导出每个分块具体的左右端点位置在哪,这个问题就可以快速求解出来了。

**推导公式:**

搬出例题:$$
\sum_{i=1}^n \lfloor \frac{n}{i} \rfloor
$$

假设我们已知某一个分块的左端点$l$,要求解出该分块的右端点$r$。设该分块的数值为$k$,对于该分块中的每个数$i$,有$k = \lfloor \frac{n}{l} \rfloor = \lfloor \frac{n}{i} \rfloor$即$i*k \le n$,也就是说我们找到可得使$i*k \le n$成立的**最大**的$i$的值即是我们所求的右端点$r$,因此我们可以得到下列式子:
$$
k = \lfloor \frac{n}{l} \rfloor
$$

$$
r = max(i) , i*k \le n
$$

推导可得:
$$
r = \lfloor \frac{n}{k} \rfloor = \lfloor \frac{n}{ \lfloor \frac{n}{l} \rfloor } \rfloor
$$

转换成代码就是:

```cpp
ans = 0;
for(int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l);
ans += n / l * (r - l + 1);
}

```

## 简单组合计数
**1**.$a,b$很小

![$$
\tbinom{n}{m} =
$$](https://img-blog.csdnimg.cn/8d709014e88f4d4ca381413ab5c8259e.png)

```csharp
#include<bits/stdc++.h>
#define LL long long
#define N 2010
using namespace std;
const LL MOD = 1e9+7;
LL c[N][N],n,a,b;
void init()
{
for(int i=0;i<=2000;i++)
{
for(int j=0;j<=i;j++)
{
if( !j ) c[i][j] = 1;
else c[i][j] = (c[i-1][j] + c[i-1][j-1] )%MOD;
}
}
}

int main()
{
cin>>n;
init();
while( n-- )
{
scanf("%lld %lld",&a,&b);
printf("%lld\n",c[a][b]);
}
return 0;
}
```


**2**.$a,b \leq10000$ ,求组合数模p之后的值
![在这里插入图片描述](https://img-blog.csdnimg.cn/d9605647e11143d3886666b7fe5414cd.png)


```csharp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 101001
#define LL long long
using namespace std;
const LL MOD = 1e9+7;
LL quick(LL a,LL b,LL p)
{
LL ans = 1;
while( b )
{
if( b&1 ) ans = ans * a % p;
a = a*a % p;
b>>=1;
}
return ans;
}

LL n,fact[N],infact[N],a,b;

void init()
{
fact[0] = infact[0] = 1;
for(int i=1;i<N;i++)
{
fact[i] = ( fact[i-1] * i ) % MOD;
infact[i] = ( infact[i-1] * quick(i,MOD-2,MOD) ) % MOD;
}
}

 

int main()
{
cin>>n;
init();
while( n-- )
{
scanf("%lld %lld",&a,&b);
printf("%lld\n", fact[a] * infact[b] % MOD * infact[a-b] % MOD );
}
return 0;
}
```
**3**. $a,b \leq 10^{18}$求组合数模p的值

卢卡斯定理
![在这里插入图片描述](https://img-blog.csdnimg.cn/2fe2b2156b1948a6a965f100e8455b34.png)

```csharp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;


LL quick(LL a,LL b,LL p)
{
LL res = 1;
while( b )
{
if( b&1 ) res = res * a % p;
a = a * a % p;
b>>=1;
}
return res;
}

LL C(LL a,LL b,LL p)
{
if( b>a ) return 0;
LL ans = 1;
for(int i=1,j=a;i<=b;i++,j--)
{
ans = ans * j % p;
ans = ans * quick( i,p-2,p ) % p;
}
return ans;
}

LL lucas(LL a,LL b,LL p)
{
if( a<p && b<p ) return C(a,b,p);
return C( a%p,b%p,p ) * lucas( a/p,b/p,p )%p;
}

int main()
{
LL n,a,b,p;
cin>>n;
while( n-- )
{
scanf("%lld %lld %lld",&a,&b,&p);
printf("%lld\n",lucas(a,b,p));
}
return 0;
}
```

posted @ 2021-12-10 18:32  ojzha  阅读(139)  评论(0)    收藏  举报