二分法大数取模

时间:2022-12-20 23:12:25

二分法
数学的魅力:

幂运算满足结合律

n为偶数: a^n=a^(n/2)*a^(n/2);
n为奇数: a^n=a^(n/2)*a^(n/2)*a;

保存a^(n/2),很容易求出a^n;

大数取模
(a*b)%m=(a%m*b%m )%m;
求(2^100000000000000)%10000;
提示:二分法,速度惊人的快速幂!!!
此题一般的数代码如下:

 

#include<iostream>
using namespace std;
#include<math.h>
__int64 fun(__int64 m,__int64 n,__int64 k)
{
    __int64 s;
    if(n==1)return m%k;
    if(n%2==0)
    {
        n/=2;
        s=fun(m,n,k)%k;
        return (s*s)%k;
    }
    else
    {
        n/=2;
        s=fun(m,n,k)%k;
        return (s*s*(m%k))%k;
    }
}
int main()
{
    freopen("d:\\1.txt","r",stdin);
    __int64 m,n,k;
    __int64 s1,s2;
    scanf("%I64d%I64d%I64d",&m,&n,&k);
    printf("%I64d\n",s1=fun(m,n,k));
    printf("%I64d\n",s2=(int)(pow(m,n))%k);//检测是否正确 只能检测2^31-1,即n<32
    if(s1==s2)printf("程序正确\n");
    else printf("不正确,程序错误- _ -\n");
    return 0;
}

 

 

(a+b)%m=(a%m+b%m)%m;
例如:若Sn为斐波那契数列前10000项的和,
求Sn%10000;

f(1)=1,f(2)=1,f(3)=2......

 方法1:一般递归方法的代码,当n很大时用时显然较长:

 

#include<iostream>
#include<time.h>
using namespace std;
int Fi(int n,int k)
{
    if(n==1||n==2)return 1%k;
    else return (Fi(n-1,k)%k+Fi(n-2,k)%k)%k;
    
}
int main()
{
    int n,k,i;
    double start=time(NULL),end;
    //freopen("d:\\1.txt","r",stdin);
    cin>>n>>k;
    int s=0;
    for(i=1;i<=n;i++)
     s=(s+Fi(i,k))%k;
     printf("%d\n",s);
    end=time(NULL);
    printf("时间=%.2lf\n",end-start);    
    
    return 0;
}

 

方法2:不用递推,先将每一个Fibonacci数的对k求余的值保存在一个数组中,这样运行时间很短,不过适用于n不是太大的情况,代码如下:

#include<iostream>
using namespace std;
#include<time.h>
int a[10010]={0};
void fun(int k)
{
    a[1]=1%k;
    a[2]=1%k;
    for(int i=3;i<=10000;i++)//先将每一个数的值保存在数组中
      a[i]=(a[i-1]%k+a[i-2]%k)%k;
}

int main()
{    
    //freopen("d:\\1.txt","r",stdin);
    double start=time(NULL),end;
    int n,k,i;
    cin>>n>>k;
    fun(k);
    int s=0;
    for(i=1;i<=n;i++)
     s=(s+a[i])%k;
     printf("%d\n",s);
     end=time(NULL);
     printf("%.2lf\n",end-start);
    return 0;
}

 方法3:

下面用矩阵法,然后对矩阵连乘用二分法:

二分法大数取模

若要求f(1)-f(10000)的和对m取模,用方法2较合适,若只求f(n)对m取模并且n非常大,比如n=1000000000,则用矩阵二分法,效果比方法2更为显著,代码如下:

#include<iostream>
using namespace std;
#include<time.h>
int a=1,b,c=1,d; //要声明为全局变量,先将a c赋值,以解决当n<3的情况
void fun(int n,int k)
{
    int t1,t2,t3,t4;
    if(n==1){a=1;b=1;c=1;d=0;return;}//注意必须加return返回 因为这是递归,必须有返回
    if(n%2==0)
    {
        n/=2;
        fun(n,k);
        
        t1=a;//必须替换原来的数,因为下面要用到
        t2=b;
        t3=c;
        t4=d;
        a=(t1*t1+t2*t3)%k;
        b=(t1*t2+t2*t4)%k;
        c=(t3*t1+t4*t3)%k;
        d=(t3*t2+t4*t4)%k;
    }
    else
    {
        n/=2;
        fun(n,k);
        
        t1=a;
        t2=b;
        t3=c;
        t4=d;
        a=(t1*t1+t2*t3)%k;
        b=(t1*t2+t2*t4)%k;
        c=(t3*t1+t4*t3)%k;
        d=(t3*t2+t4*t4)%k;
        int t5=a,t6=c;
        a=(t5+b)%k;
        b=t5%k;
        c=(t6+d)%k;
        d=t6%k;
    }
}
int main()
{
    freopen("d:\\1.txt","r",stdin);
    double start=time(NULL),end;
    int n,k;
    cin>>n>>k;
    if(n<3)
    printf("%d\n",(a+c)%k);
    else
    {
    fun(n-2,k);
    printf("%d\n",(a+c)%k);
    }
    end=time(NULL);
    printf("%.2lf\n",end-start);
    return 0;
}