acm数论之矩阵快速幂+快速幂

 
一.取模运算的性质:

(1)(a+b)%c=(a%c+b%c)%c
(2)(ab)%c=(a%c)(b%c)%c 

快速幂的复杂度分析:o(log n)
快速幂的原理
采用位运算的两种工具:
&:这里用作分析数的奇偶性,x&1==1数为奇数,x&1==0数为偶数
>>:这里是二进制的所有位向右移一位,一般可以看作是log(2) n,但是与直接以2为底数取对数不同,比如数1,所得到的结果完全不同,所以我们这直接使用位运算>>更合适。
运用矩阵相乘的相关知识:
两个矩阵的乘法仅当第一个矩阵A的列数和另一个矩阵B的行数相等时才能定义。如A是m×n矩阵和B是n×p矩阵,它们的乘积C是一个m×p矩阵
  
它的一个元素:
例如
 1 juzh chengfa(juzh kk,juzh ll)
 2 {
 3     juzh result;
 4     memset(result.a,0,sizeof(result.a));
 5     for(int i=0;i<len;i++)
 6     for(int j=0;j<len;j++)
 7     for(int h=0;h<len;h++)
 8     result.a[i][j]=(result.a[i][j]+kk.a[i][h]*ll.a[h][j])%mmod; 
 9     return result;
10 }
快速幂就是采用二分思想快速取幂
a^b=a*a*a*...
分析b:
如果b为偶数:a^b=a^(b/2*2)=a^(b/2)*a^(b/2)
如果b为奇数:a^b=a*a^((b-1)/2*2)=a*a^(b-1/2)*a^(b-1/2)
这里我们只讨论二分一次的结果:我们可以看出来原本的a^b变成了a^(b/2)^2,如果这时使用暴力法,我们只需进行(for(int i=1;i<=n/2;i++),一下子减少了一半。
后面继续二分,直到b分为1,操作次数差不多是o(log n)
 
快速幂的关键代码
 1 long long solve(int a,int b,int mod)
 2 {
 3     long long ans=1;
 4     while(b!=0)//b>0 
 5     {
 6         if(b&1)//说明b为奇数,分成a*ans
 7         ans=(ans*a)%mod;
 8         a=a*a;
 9         b>>=1;
10     }
11     return ans;
12 }
快速幂适合解决的问题
1、快速幂取模(a^b) mod (m)(a,b都是非常大的数据时)---减少时间复杂度
算法的原理是a^b mod c=(a mod c)(b mod c)mod c 
在面对a^b(a,b都是非常大的数据时)时,假如使用暴力法(即for(int i=1;i<=n;i++)a*=a;),很明显复杂度为o(n)
而采用了二分思想的快速幂,复杂度为o(logn)
注明:快速幂和暴力法一样当数据过大如果数据不进行取模这一步骤时,数据很有可能溢出,但是快速幂的优势在于复杂度低,所用时间快
2.矩阵快速幂
#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
using namespace std;
int n,m;//m即mod 
struct node{
    int a[35][35];
};
node ONE;
node multi(node X,node Y)//X*Y矩阵相乘 
{
    node R;
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    {
        R.a[i][j]=0;
        for(int k=0;k<n;k++)
        R.a[i][j]=(R.a[i][j]+X.a[i][k]*Y.a[k][j])%m;
    }
    return R;
}
node add(node X,node Y)//X+Y矩阵相加 
{
    node R;
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    {
        R.a[i][j]=(X.a[i][j]+Y.a[i][j])%m; 
    }
    return R;
}
node factorial(node X,int t)//X矩阵的t次方 
{
    node ans;
    memset(ans.a,0,sizeof(ans.a));
    for(int j=0;j<n;j++)//初始化为单位矩阵 
    ans.a[j][j]=1;
    
    while(t!=0)
    {
        if(t&1)
        ans=multi(ans,X);
        X=multi(X,X);
        t>>=1;
    }
    return ans;
}

 

可以解决f(n)与f(n-1)类的式子的计算
例如:
题目要求:f(n)=2*f(n-1)+1(n为奇数)f(n)=2*f(n-1)(n为偶数),f(0)=0,1<=n, m <= 1000000000;输入n,m(模)求出f(n)mod m的值
分析题目n>=1,m>=1000000000;如果直接暴力,结果一定是超时
这类题目采用矩阵快速幂的方法进行解决。
假设幂矩阵为kk,答案矩阵(包含题中所求的f[n])为ans,过渡矩阵(即包含f[n-1]的矩阵)为hh
构造矩阵数学式:
注意我们只讨论偶数(使用偶数方便,n/2=1,2,3...)构造矩阵求解,奇数的结果可以看成上一个数*2+1
分析题目数据:1,2,5,10,21,42,可以知道偶数规律:f1(s)=4*f1(s-1)+2;(注意这里是单拿出来偶数,s=n/2,n=2的数据f(2)是s=1的f1(1)
kk*hh=ans;
4  2   f[n-1]      f[n]
0  1     1          1
(ps:学习矩阵快速幂前需要了解两矩阵如何相乘等基础操作)
4  2   f[n-2]    f[n-1]
0  1     1          1
 
这个kk矩阵的n次方,kk^n*hh=ans,f[n]=ans.a[0][0];hh= 0
4  1    f[0]        f[n]                            1
0  1      1          1
解决代码:
 1 #include<iostream>
 2 #include<string>
 3 #include<string.h>
 4 using namespace std;
 5 #define len 2
 6 int mmod;
 7 struct juzh{
 8     long long a[len][len];
 9 }; 
10 juzh chengfa(juzh kk,juzh ll)
11 {
12     juzh result;
13     memset(result.a,0,sizeof(result.a));
14     for(int i=0;i<len;i++)
15     for(int j=0;j<len;j++)
16     for(int h=0;h<len;h++)
17     result.a[i][j]=(result.a[i][j]+kk.a[i][h]*ll.a[h][j])%mmod; 
18     return result;
19 }
20 long long solve(juzh kai,int b)//a^b%mod 
21 {
22     juzh ans;//ans为单位矩阵 
23     ans.a[0][0]=1;
24     ans.a[0][1]=ans.a[1][0]=0;
25     ans.a[1][1]=1;
26     while(b!=0)//b>0 
27     {
28         if(b&1)
29         ans=chengfa(ans,kai);
30         kai=chengfa(kai,kai);
31         b>>=1;
32     }
33     return ans.a[0][1];
34 }
35 //这里需要注意分开偶数与奇数 
36 int main()
37 {
38     int n;
39     while(cin>>n>>mmod)
40     {
41         juzh kai;
42         kai.a[0][0]=4,kai.a[0][1]=2,kai.a[1][0]=0,kai.a[1][1]=1;
43         if(n&1)//说明为奇数
44         cout<<(solve(kai,(n-1)/2)*2+1)%mmod<<endl;
45         else
46         cout<<solve(kai,n/2)<<endl;
47     }
48 }
hdu 4990
采用矩阵快速幂方法的步骤:
1.构建kk幂矩阵,ans矩阵,hh过渡矩阵
2.幂矩阵与初始ans矩阵乘n次(ans矩阵初始化是单位矩阵:ans[i][i]=1)
3.kk^n*E(E为单位矩阵)*hh=ans,分析ans,根据矩阵相乘相关知识得出f(n)
3.求取大数的后k位

例题:lightoj 1282 Leading and Trailing

 1 #include <cstdio>
 2 #include<algorithm>
 3 #include<string.h>
 4 #include<string>
 5 #include<algorithm>
 6 int MOD;
 7 struct Matrix
 8 {
 9     long long a[2][2];//幂矩阵的大小 
10     Matrix () {}
11     Matrix operator * (Matrix const &b)const
12     {
13         Matrix res;
14         memset(res.a,0,sizeof(res.a));   //矩阵相加 a[i][j]=sum(b[i][k]+c[k][j]); 
15         for (int i = 0; i < 2; i++)//a.x即i 
16             for (int j = 0; j < 2; j++) //a.y即j 
17                 for (int k = 0; k < 2; k++)  //b.y即k 
18                     res.a[i][j] = (res.a[i][j] + this->a[i][k]  * b.a[k][j]) % MOD;
19         return res;
20     }
21 };
22 Matrix pow_mod(Matrix base, int n)
23 {
24     Matrix ans;
25     memset(ans.a,0,sizeof(ans.a));//设置ans的初始矩阵值 
26     ans.a[0][0] = ans.a[1][1]=1;
27     while (n > 0)
28     {
29         if (n & 1)
30             ans = ans * base;
31         base = base * base;
32         n >>= 1;
33     }
34     return ans;
35 }
36 int main()
37 {
38     Matrix base;//设置幂矩阵的值
39     base.a[0][0] = 1;
40     base.a[0][1] = 1;
41     base.a[1][0] = 1;
42     base.a[1][1] = 0;
43     int n;
44     while (~scanf("%d%d", &n, &MOD))
45     {
46        Matrix ans = pow_mod(base, n);//得到存放f[n]的矩阵 
47        printf("%d\n",ans.a[1][0]);// f[n]存放在ans矩阵中的a[1][0]这个位置(看具体自己设的矩阵公式) 
48     }
49 }
50 
51 poj 3070
Poj 3070
 1 #include<iostream>
 2 #include<algorithm>
 3 using namespace std;
 4 struct node{
 5     long long a[2][2];
 6 };
 7 long long m1;
 8 node chengfa(node m,node n)
 9 {
10     node k;
11     for(int i=0;i<2;i++)
12     for(int j=0;j<2;j++)
13     k.a[i][j]=0;
14     for(int i=0;i<2;i++)
15     for(int j=0;j<2;j++)
16     for(int k1=0;k1<2;k1++)
17     k.a[i][j]=(k.a[i][j]+m.a[i][k1]*n.a[k1][j])%m1;
18     return k;
19 }
20 node solve(node m,int n)
21 {
22     node ans;
23     ans.a[0][0]=ans.a[1][1]=ans.a[1][0]=0;
24     ans.a[0][1]=1;
25     while(n>0)
26     {
27         if(n&1!=0)
28         ans=chengfa(ans,m);
29         m=chengfa(m,m);
30         n>>=1;
31         }
32     return ans;    
33 }
34 int main() 
35 { 
36   long long n; 
37   node t,ans;
38   t.a[0][0]=4;
39   t.a[0][1]=0;
40   t.a[1][1]=1;;
41   t.a[1][0]=2;
42   while(scanf("%lld%lld",&n,&m1)!=EOF) 
43   { 
44      ans=solve(t,n/2);
45      if(n%2==0)
46      cout<<(ans.a[0][0])%m1<<endl;
47      else
48      cout<<(ans.a[0][0]*2+1)%m1<<endl;
49       } 
50   return 0; 
51 }
hdu 4990

 

快速幂的应用
1.矩阵快速幂——矩阵包含矩阵    poj 3233   二分思路+矩阵幂(只是类似快速幂思想,并未用到快速幂)

 如果k为偶数,那么(A+A^2+....A^K) = (A+...+A^K/2)+A^K/2*(A+...+A^K/2)

如果k为奇数,那么(A+A^2+....A^K) = (A+...+A^K/2)+A^K/2*(A+...+A^K/2)+A^k

 A + A^2 + A^3 + A^4 + A^5 + A^6 =(A + A^2 + A^3) + A^3*(A + A^2 + A^3)
    应用这个式子后,规模k减小了一半。我们二分求出A^3后再递归地计算A + A^2 + A^3,即可得到原问题的答案。
if(n%2==0)  先求num=A+...+A(n/2)   然后ans=num+num*A^n/2
else    n=n-1  先求num=A+...+A(n/2)   然后ans=num+num*A^n/2+A^(n+1)
最易理解的代码:
 1 #include<iostream>
 2 #include<algorithm>
 3 #include<string>
 4 #include<string.h>
 5 using namespace std;
 6 int n,m;
 7 struct node{
 8     int a[35][35];
 9 };
10 node ONE;
11 node multi(node X,node Y)//X*Y矩阵相乘 
12 {
13     node R;
14     for(int i=0;i<n;i++)
15     for(int j=0;j<n;j++)
16     {
17         R.a[i][j]=0;
18         for(int k=0;k<n;k++)
19         R.a[i][j]=(R.a[i][j]+X.a[i][k]*Y.a[k][j])%m;
20     }
21     return R;
22 }
23 node add(node X,node Y)//X+Y矩阵相加 
24 {
25     node R;
26     for(int i=0;i<n;i++)
27     for(int j=0;j<n;j++)
28     {
29         R.a[i][j]=(X.a[i][j]+Y.a[i][j])%m; 
30     }
31     return R;
32 }
33 node factorial(node X,int t)//X矩阵的t次方 
34 {
35     node ans;
36     memset(ans.a,0,sizeof(ans.a));
37     for(int j=0;j<n;j++)//初始化为单位矩阵 
38     ans.a[j][j]=1;
39     
40     while(t!=0)
41     {
42         if(t&1)
43         ans=multi(ans,X);
44         X=multi(X,X);
45         t>>=1;
46     }
47     return ans;
48 }
49 node solve(node X,int t)
50 {
51     if(t==1)
52     return X;
53     if(t%2==0)
54     {
55         node B=solve(X,t/2);
56         return multi(add(factorial(X,t/2),ONE),B);//num*(E+A^(t/2))
57     }
58     else
59     {
60         
61         node B=solve(X,t/2);
62 //        第一种 num*(E+A^(t/2))+A^t----时间复杂度高 
63         node C=factorial(X,t);
64         return  add(C,multi(add(factorial(X,t/2),ONE),B));//num*(E+A^(t/2))+A^t
65 //        第二种 A^((n+1)/2)+num*(E+A^((n+1)/2))
66 //      node C=factorial(X,(t+1)/2);----时间复杂度低 
67 //        return add(C,multi(B,add(C,ONE)));   //A^((n+1)/2)+num*(E+A^((n+1)/2))
68 
69     }
70 }
71 int main()
72 {
73     node M;
74     int k;
75     scanf("%d%d%d",&n,&k,&m);
76     for(int i=0;i<n;i++)
77     for(int j=0;j<n;j++)
78     scanf("%d",&M.a[i][j]);
79     for(int i=0;i<n;i++)//单位矩阵 
80     ONE.a[i][i]=1;
81     
82     node ans=solve(M,k);
83     for(int i=0;i<n;i++){
84     for(int j=0;j<n;j++)
85     {
86         if(j!=0)
87         printf(" ");
88         printf("%d",(ans.a[i][j])%m);
89     }
90     printf("\n");
91     }
92     return 0;
93  } 
poj 323
 
总结的非常好的题集:https://www.xuebuyuan.com/3259372.html
 
posted @ 2019-04-16 15:47  saaas  阅读(262)  评论(0编辑  收藏  举报