大数取模:一般取模 + 技巧取模 + 快速幂取模 + 欧拉函数(费马小定理) 附简单题解

介绍四种取模方法前,先了解一下真正意义的大数取模;
mod=1e9+7 ; 现在给出一个超大的数,不是一般的大哦,假设这个数的位数是400位吧; 那么直接去取模结果是会出错的,下面分析一下;
问题分析:
(1)大数存储:由于x的位数最大为400位,我们不能用现有的int,long,long long,double等数据类型进行存储。一般存储大数的方法是用一个字符串来表示。
(2)取模运算:模拟手算竖式的方法。用x从高到低的每一位加上前一位余数*10来对bi进行%,最后得到的结果就是x%bi的结果。

int divMod(char* ch,int num)
{
	int s = 0;
	for(int i=0;ch[i]!='\0';i++)
		s = (s*10+ch[i]-'0')%num;
	
	return s;
}

这是对于直接给你一个超大数让你取模的办法,但有时候不会直接给你一个超大数,而是这样 2^128 。所以就出来了另外几种取模办法;

求 a^n%b
1.一般取模运算(时间复杂度太大):

(a^n)%m。 我们可以改写为(a^n)%m= ((a%m)^n)%m, 即循环n次。
缺点:低效,循环了n次。

int exp_mod(int a,int n,int b)
{
    a=a%b;
    int temp=1;
    while(n--)
    {
        temp=temp*a;
        temp=temp%b;
    }
    return temp;
}
2.快速幂取模:(良好的取模复杂度)

2^128%10

#include 

using namespace std;
int Prowermod(int a,int n,int b)
{
    int ans=1;
    a=a%b;
    while(n)
    {
        if(n&1) ans=ans*a%b;
        n/=2;
        a=a*a%b;
    }
    return ans;
}
int main()
{
    int x=Prowermod(2,128,10);
    cout <<x<<endl;
    return 0;
}
3.欧拉函数(费马小定理)

先介绍一个定义,互质:a和b互质,即a和b除了1以为,没有其他公约数。
对正整数n,欧拉函数是小于n的数中与n 互质的数的数目,
φ(8)=4,因为1,3,5,7均和8互质。

费马小定理
假如p是质数,且gcd(a,p)=1,那么 a(p-1)≡1(mod p)。即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。

同余证法:
任意取一个质数,比如13。考虑从1到12的一系列整数1,2,3,4,5,6,7,8,9,10,11,12,给这些数都乘上一个与13互质的数,比如3,得到3,6,9,12,15,18,21,24,27,30,33,36。对于模13来说,这些数同余于3,6,9,12,2,5,8,11,1,4,7,10。这些余数实际上就是原来的1,2,3,4,5,6,7,8,9,10,11,12,只是顺序不同而已。
把1,2,3,…,12统统乘起来,乘积就是12的阶乘12!。把3,6,9,…,36也统统乘起来,并且提出公因子3,乘积就是3{12}×12!。对于模13来说,这两个乘积都同余于1,2,3,…,12系列,尽管顺序不是一一对应,即3{12}×12!≡12!mod 13。两边同时除以12!得3^{12}≡1 mod 13。如果用p代替13,用x代替3,就得到费马小定理x^{p-1}≡1 mod p。

3.技巧取模(借鉴其他大神的博客)

第一种,技巧取模:
(a^n)%10
当n非常大时,嗯,只能用字符串存n的时候。

简单分析一下,
a%10. 有10种可能,(来源于室友“张博士”的逆天发现,明明可以靠脸吃饭,他偏偏要靠才华,明明可以快速幂取模,它偏要发现这种,出现大大大大数的)

a%10 = 0. 这个结果就不需要看了。0
a%10 = 1. (1^n )%10会出现的可能数字:1
a%10 = 2. (2^n )%10会出现的可能数字:2,4(=22), 8(=24),6(=2*8),继续循环,2, 4, 8, 6……

a%10 = 3. (3^n )%10会出现的可能数字:3,9(=33),7(=39), 1(=3*7),继续循环,3,9,7,1……

a%10 = 4. (4^n )%10会出现的可能数字:4,6(4*4) , 继续循环,4,6……

a%10 = 5. (5^n )%10会出现的可能数字:5
a%10 = 6. (6^n )%10会出现的可能数字:6
a%10 = 7. (7^n )%10会出现的可能数字:7, 9, 3, 1 继续循环,
a%10 = 8. (8^n )%10会出现的可能数字:8, 4, 2, 6 继续循环,
a%10 = 9. (9^n )%10会出现的可能数字:9, 1 继续循环,

重点是继续循环,发现,最大情况下,都是以4位数字循环,换句话说,(a^n)%10 和(a^(n+4))%10是相等的,当然,这里n不等于0. 如果这里看懂了,那这里差不多就ok了。

我们把n –> (n%4)+4. 这里加4的原因是为了防止n%4==0,并且我们没有考虑n=0的情况。这个单独处理一下。

OK
如果n != 0.
(a^n)%10 = (a^(n%4+4))%10 = ((a%10)^(n%4+4)) % 10

另外友情提示对于n,如果是字符串存储,(十进制) 我们只需要取最后的2位数,用它代替n即可,因为999, 900肯定是可以被4整除的,我们只需要99即可,用99%4+4。
如果是int 型或longlong。(二进制) (n & 11)按位“与”可以取出后2位,代替n%4,(注意不能代替n%4+4 . 加4一直需要),第3位是4,肯定可以被4整除。

题解稍后再来 睡会…

原题 1:hdu 1212

原题的意思大概是给你一个不超过1000位的数 a ,再输入一个不超过100000的数 b
求 a%b

这道题就是用到开头的取模方法
AC代码

#include 

using namespace std;
int main()
{
    char cc[1000];
    long long int b;
    while(cin >>cc>>b)
    {
        int sum=0;
        for(int i=0;cc[i]!='\0';i++)
        {
            sum=sum*10+cc[i]-'0';
            sum%=b;
        }
        cout <<sum<<endl;
    }
    return 0;
}

原题 2:hdu - 2098

题意很明确,那就直接上代码
AC代码

#include 

using namespace std;
typedef long long int ll;
int main()
{
    ll a ,b;
    while(cin >>a>>b)
    {
        if(a==0&&b==0) break;
        a=a%1000;
        int ans=1;
        while(b)
        {
            if(b&1) ans= ans*a%1000;
            b/=2;
            a=a*a%1000;
        }
        cout <<ans<<endl;
    }
    return 0;
}

今天到此为止,关于费马小引理的题,之后我搜一搜继续更新

你可能感兴趣的:(大数取模:一般取模 + 技巧取模 + 快速幂取模 + 欧拉函数(费马小定理) 附简单题解)