模运算
( a + b ) % p = ( a % p + b % p ) % p ( a + b ) \% p = ( a \% p + b \% p ) \% p (a+b)%p=(a%p+b%p)%p
( a ∗ b ) % p = ( ( a % p ) ∗ ( b % p ) ) % p ( a * b ) \% p = ( (a \% p ) * (b \% p ) ) \% p (a∗b)%p=((a%p)∗(b%p))%p
c ∗ ( a % p ) = ( c ∗ a ) % ( c ∗ p ) c * ( a \% p ) = ( c *a ) \% ( c *p ) c∗(a%p)=(c∗a)%(c∗p)
欧几里得算法
对于任意两个正整数 a,b ,都有:
a
=
k
b
+
r
(
k
,
r
∈
N
)
a=kb+r (k,r∈N)
a=kb+r(k,r∈N)
所以有:
r
=
a
r=a%b
r=a
然后我们假设 c 是 a 和 b 的最大公约数,即
c
=
g
c
d
(
a
,
b
)
c=gcd(a,b)
c=gcd(a,b)
然后,我们就能得到:
c
∣
a
和
c
∣
b
(
x
∣
y
表
示
x
能
够
整
除
y
,
y
能
被
x
整
除
,
也
就
是
y
/
x
是
整
数
)
c|a 和c|b (x|y 表示 x 能够整除 y , y能被x整除 , 也就是y/x是整数)
c∣a和c∣b(x∣y表示x能够整除y,y能被x整除,也就是y/x是整数)
然后又因为上面那个式子,有:
r
=
a
−
k
b
r=a−kb
r=a−kb
c
∣
r
c|r
c∣r
c
=
g
c
d
(
b
,
r
)
c=gcd(b,r)
c=gcd(b,r)
即
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
%
b
)
gcd(a,b)=gcd(b,a\%b)
gcd(a,b)=gcd(b,a%b)
而且
g
c
d
(
a
,
0
)
=
a
gcd(a,0) = a
gcd(a,0)=a
辗转相除法函数代码:
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;
}
扩欧算法
例题1
求关于
x
x
x的同余方程
a
x
≡
1
(
m
o
d
b
)
a x \equiv 1 \pmod {b}
ax≡1(modb) 的最小正整数解.输入数据保证一定有解
乘法逆元
算逆元的三个方法:
第一个方法:
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;
}
.
.
.
.
第二个方法:
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;
}
.
.
.
.
第三个方法:
这里有一个模板题目 题目传送门
#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)是完全积性函数
#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为素数)
①
P
h
i
(
p
)
=
p
−
1
Phi(p)=p-1
Phi(p)=p−1
②
若
已
知
P
h
i
(
x
)
,
p
r
j
能
整
除
x
:
P
h
i
(
x
∗
)
=
P
h
i
(
x
)
∗
p
若已知Phi(x),pr_j能整除x: Phi(x*)=Phi(x)*p
若已知Phi(x),prj能整除x:Phi(x∗)=Phi(x)∗p
③
若
已
知
P
h
i
(
x
)
,
且
p
不
能
整
除
x
:
P
h
i
(
x
∗
p
r
j
)
=
P
h
i
(
x
)
∗
(
p
r
j
−
1
)
若已知Phi(x),且p不能整除x:Phi( x* pr_j )=Phi(x)*(pr_j-1)
若已知Phi(x),且p不能整除x:Phi(x∗prj)=Phi(x)∗(prj−1)
简单地证明上述式子:对于①,由于
p
p
p本身是一个素数,那么比它小的所有数都和它互质,因此答案为
p
−
1
p-1
p−1。对于②,
p
p
p已经是x的质因子,因此我们看看欧拉函数的定义式可以的出括号相乘部分不会改变,只会把最前面的
x
x
x变成
x
∗
p
x*p
x∗p。对于③
p
p
p是新加入的质因子,相当于式子前面多乘了个p,然后多加了一个括号,所以相当于原式多乘了:
p
∗
(
1
−
1
/
p
)
即
(
p
−
1
)
p*(1-1/p)即(p-1)
p∗(1−1/p)即(p−1)。
#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,求
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^n \lfloor \frac{n}{i} \rfloor
i=1∑n⌊in⌋
我们写一个表格看一看1-20的整除是什么样子的
表中同样的值会连续出现,而相同的值所划分的区间积是整出分块。整除的性质使得从1到n的数组表可根据数值划分为不同的分块,且分块数远远小于n。利用这种性质,我们如果能推导出每个分块具体的左右端点位置在哪,这个问题就可以快速求解出来了。
推导公式:
搬出例题: ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^n \lfloor \frac{n}{i} \rfloor i=1∑n⌊in⌋
假设我们已知某一个分块的左端点
l
l
l,要求解出该分块的右端点
r
r
r。设该分块的数值为
k
k
k,对于该分块中的每个数
i
i
i,有
k
=
⌊
n
l
⌋
=
⌊
n
i
⌋
k = \lfloor \frac{n}{l} \rfloor = \lfloor \frac{n}{i} \rfloor
k=⌊ln⌋=⌊in⌋即
i
∗
k
≤
n
i*k \le n
i∗k≤n,也就是说我们找到可得使
i
∗
k
≤
n
i*k \le n
i∗k≤n成立的最大的
i
i
i的值即是我们所求的右端点
r
r
r,因此我们可以得到下列式子:
k
=
⌊
n
l
⌋
k = \lfloor \frac{n}{l} \rfloor
k=⌊ln⌋
r = m a x ( i ) , i ∗ k ≤ n r = max(i) , i*k \le n r=max(i),i∗k≤n
推导可得:
r
=
⌊
n
k
⌋
=
⌊
n
⌊
n
l
⌋
⌋
r = \lfloor \frac{n}{k} \rfloor = \lfloor \frac{n}{ \lfloor \frac{n}{l} \rfloor } \rfloor
r=⌊kn⌋=⌊⌊ln⌋n⌋
转换成代码就是:
ans = 0;
for(int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l);
ans += n / l * (r - l + 1);
}
例题:
求
∑
i
=
1
n
⌊
n
a
x
+
b
⌋
求 \sum_{i=1}^n \lfloor \frac{n}{ax+b} \rfloor
求i=1∑n⌊ax+bn⌋
简单组合计数
1. a , b a,b a,b很小
#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
≤
10000
a,b \leq10000
a,b≤10000 ,求组合数模p之后的值
#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 ≤ 1 0 18 a,b \leq 10^{18} a,b≤1018求组合数模p的值
卢卡斯定理
#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;
}