和与余数的和同余理解_同余相关算法小结

本文深入探讨了同余的概念、性质及其在算法解题中的应用,包括加减乘法的同余性质、除法的特殊性、逆元的求解以及九余数定理。通过实例解析了如何利用同余性质解决ACM竞赛中的问题,如求解数位和、大数分解与合并等。同时,文章介绍了中国剩余定理在解决线性同余方程组中的作用,并给出了相关算法题目的解决方案。
摘要由CSDN通过智能技术生成

1b40485607af1d1e76a29d30bffa21c0.png

f396f81ace9f8fc38bcbd4178253bf90.png
《飞屋环游记》

形如

同余方程的求解,以及同余定理的应用常常隐藏在很多算法题目的解法中。

为什么取余满足加法,减法和乘法而不满足除法?

为什么求解逆元的多种方法有不同的条件,而且如何认识方法的正确性?

如何理解九余数定理?

.....

通过对同余的了解和对同余定理的学习,我们能够对很多常见的结论有更深的认识。

同时有助于对其他相关算法的学习。

同余的概念

对于同余,常见的下面这个式子:

其数学意义是

,即
的模相同。

因为

取余后有相同的余数

故若

做差,则相同的余数一定会被抵消掉,即

假设

则上述同余式可化成:

这也是解决相关算法题目时常使用的一种转化形式。

同余的性质

首先是学过离散数学的朋友都知道的三大基本性质:自反性,对称性,传递性,即:

  • 如果
    ,则
  • 如果

由以下三条性质,取模满足加法、减法和乘法的性质:

如果

而为什么除法不满足取模呢,那是因为除法满足的同余性是:

  • 如果
    ,则

简单证明如下:

,转化后得出:

等号两边同除

得:

为任意整数,而模数也应该是任意小的整数,则:

,即

再补充几个重要性质:

  • 如果
  • 若有

九余数定理

以上两个数论知识就不详细介绍了,来看这么一道题。

Problem - 1163​acm.hdu.edu.cn

题意

给你一个正整数

,每次将所有位数加起来得到一个新的数,直到新的数只有一位。输出最后的结果。

思路

进行举例:

如果把数按十进制展开:

每一次操作实质上是将每一位的权值(十位的10,百位的100)变成了1。

如果只是将10变成1,我们可以考虑:

  • ......

但是,我们同时需要将100,1000甚至100000....都变成1,所以,只有对9取模才能达到这样的效果。

由离散数学,在这里

变成了一个等价类,只有同余关系满足自反,对称和传递性,所以才满足条件。

所以本题只需要求出该数对9取模的数就好了。

但存在一个特殊情况是:

,所以应该特判答案为0的情况。

代码

#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 - 6108​acm.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 -- Biorhythms​poj.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 - 4767​acm.hdu.edu.cn

此题叫我们求出模

的第
项Bell数。

因为

的范围很大,按照常规办法预处理第二类斯特林数求和或者打表一个贝尔三角形在时间和空间复杂度两方面均是爆炸的。

所以我们可以考虑利用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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值