形如
为什么取余满足加法,减法和乘法而不满足除法?
为什么求解逆元的多种方法有不同的条件,而且如何认识方法的正确性?
如何理解九余数定理?
.....
通过对同余的了解和对同余定理的学习,我们能够对很多常见的结论有更深的认识。
同时有助于对其他相关算法的学习。
同余的概念
对于同余,常见的下面这个式子:
其数学意义是
因为
故若
假设
则上述同余式可化成:
这也是解决相关算法题目时常使用的一种转化形式。
同余的性质
首先是学过离散数学的朋友都知道的三大基本性质:自反性,对称性,传递性,即:
-
- 如果
,则
- 如果
和则
由以下三条性质,取模满足加法、减法和乘法的性质:
如果
而为什么除法不满足取模呢,那是因为除法满足的同余性是:
- 如果
,则
简单证明如下:
由
等号两边同除
因
再补充几个重要性质:
- 如果
则
- 若有
且则
九余数定理
以上两个数论知识就不详细介绍了,来看这么一道题。
Problem - 1163acm.hdu.edu.cn题意
给你一个正整数
思路
以
如果把数按十进制展开:
每一次操作实质上是将每一位的权值(十位的10,百位的100)变成了1。
如果只是将10变成1,我们可以考虑:
-
-
-
- ......
但是,我们同时需要将100,1000甚至100000....都变成1,所以,只有对9取模才能达到这样的效果。
由离散数学,在这里
所以本题只需要求出该数对9取模的数就好了。
但存在一个特殊情况是:
代码
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mod = 9;
int main(){
int n;
while(~scanf("%d",&n) && n){
int ans = 1;
for(int i=1 ;i<=n ;i++) ans = (ans*n)%mod;
if(ans == 0) puts("9");
else printf("%dn",ans);
}
return 0;
}
巧合的是,一个数对
仔细思考容易发现,在十进制下,
那么在任意的
这就是2017年百度之星初赛(A)的一道题:
Problem - 6108acm.hdu.edu.cn我们可以试着用同余的性质来解决该问题。
假设
我们仍然用
由乘法的同余性可见:
故
对于此题,也就转化成了求约数个数的经典问题。
代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 1e5 + 10;
bool vis[A];
int pri[A],tot;
void init(){
tot = 0;
for(int i=2 ;i<A ;i++){
if(vis[i] == 0) pri[++tot] = i;
for(int j=1 ;j<=tot && i*pri[j]<A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0) break;
}
}
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
n--;
ll ans = 1;
for(int i=1 ;i<=tot && pri[i]<=n ;i++){
if(n % pri[i] == 0){
int cnt = 0;
while(n%pri[i] == 0){
n /= pri[i];
cnt++;
}
ans *= (cnt+1);
}
}
if(n>1) ans*=2;
printf("%I64dn",ans);
}
return 0;
}
线性同余方程组
下面介绍中国剩余定理,形如下面的同余方程组:
若
则该方程组在
其中:
下面是一道例题:
1006 -- Biorhythmspoj.org中国剩余定理算法裸题
代码
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 5;
int a[A],m[A];
int M;
void exgcd(int a,int b,int &x,int &y){
if(b == 0){
x = 1;y = 0;
return;
}
exgcd(b,a%b,x,y);
int t = x;
x = y;y = t - (a/b)*y;
}
int CRT(int a[],int m[],int n){
M = 1;
for(int i=1 ;i<=n ;i++) M*=m[i];
int ans = 0;
for(int i=1 ;i<=n ;i++){
int x,y;
int M_i = M / m[i];
exgcd(M_i,m[i],x,y);
x = (x%m[i] + m[i])%m[i];
ans = (ans + a[i]*M_i*x)%M;
}
ans = (ans+M)%M;
return ans;
}
int main(){
int p,e,i,d,_=1;
while(~scanf("%d%d%d%d",&p,&e,&i,&d)){
if(p==-1&&e==-1&&i==-1&&d==-1) break;
a[1] = p,a[2] = e,a[3] = i;
m[1] = 23,m[2] = 28,m[3] = 33;
int ans = ((CRT(a,m,3) - d)%M + M)%M;
if(ans == 0) ans = M;
printf("Case %d: the next triple peak occurs in %d days.n",_++,ans);
}
return 0;
}
因
代码
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mod = 23*28*33;
int main(){
int a = 5544,b = 14421,c = 1288;
int p,e,i,d,_=1;
while(~scanf("%d%d%d%d",&p,&e,&i,&d)){
if(p==-1&&e==-1&&i==-1&&d==-1) break;
int ans = ((p*a+e*b+c*i-d)%mod + mod)%mod;
if(ans == 0) ans = mod;
printf("Case %d: the next triple peak occurs in %d days.n",_++,ans);
}
return 0;
}
但当
考虑下面的两个同余方程:
转化后得到:
化简得:
移项得:
由拓展欧几里德,知:
当
否则应用拓展欧几里德解出
故最后可合并为:
下面是中国剩余定理的另一个重要应用:
大数分解与合并
这也是在计算机科学中常见的应用。
假定
由中国剩余定理知:
对于
举个栗子:
现有
则
比如我们现在要计算
我们可以先将两个数表示成他们对
由
故:
同理:
当求和时,只需要以下步骤:
因为每一个四元坐标对应唯一的小于
故可用中国剩余定理求出:
下面是一道例题:
Problem - 4767acm.hdu.edu.cn此题叫我们求出模
因为
所以我们可以考虑利用Bell数的两个重要的同余性质。(
此时我们可以注意到题目给了一个奇怪的模数
分解因子后易得:
故由我们上面介绍的例子,我们可以分别求出
而每个模数
利用性质1可构建矩阵使用矩阵快速幂
而性质2可将n转化为
代码
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;
const int P = 95041567;
const int N = 50;
int a[5] = {31,37,41,43,47};
int f[N],s[2][N];
int i,j,x;
ll n;
void init(){
f[0] = f[1] = s[0][0] = 1;
s[0][1]= 2;
for(i=2,x=1;i<N ;i++,x^=1) for(f[i]=s[x][0]=s[x^1][i-1],j=1;j<=i;j++)
s[x][j] = (s[x^1][j-1] + s[x][j-1])%P;
}
ll cal(int x,ll n){
int m = 0,b[N],c[N],d[70];
for(i=0 ;i<=x ;i++) b[i] = f[i]%x;
while(n) d[m++] = n%x,n/=x; //将n转化为x进制
for(i=1 ;i<m ;i++) for(j=1 ;j<=d[i] ;j++){
for(int k=0 ;k<x ;k++) c[k] = (b[k]*i + b[k+1])%x;
c[x] = (c[0]+c[1])%x;
for(int k=0 ;k<=x ;k++) b[k] = c[k];
}
return c[d[0]];
}
ll pow(ll a,ll b,ll p){ll t=1;for(a%=p;b;b>>=1LL,a=a*a%p) if(b&1LL) t=t*a%p;return t;}
ll bell(ll n){
if(n<N) return f[n];
ll t = 0;
for(int i=0;i<5;i++) t = (t + (P/a[i])*pow(P/a[i],a[i]-2,a[i])%P*cal(a[i],n)%P)%P; //CRT合并
return t;
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
scanf("%I64d",&n);
printf("%I64dn",bell(n));
}
return 0;
}