• 二分法大数取模


    二分法
    数学的魅力:

    幂运算满足结合律

    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;
    }

     

  • 相关阅读:
    代码重构编译---make
    clickhouse日期函数
    连续登陆天数+最大登陆天数
    clickhouse基本使用
    数组
    CK优化
    Hive查询优化~布隆过滤器使用
    Presto常见问题优化
    Presto原理解析
    几种排序说明
  • 原文地址:https://www.cnblogs.com/hsqdboke/p/2470733.html
Copyright © 2020-2023  润新知