快速幂取模
参考博客
讲的很好,引用一下
但是有一个错误:
我们的a^b%c运算就可以转化为
(a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
————外面少了个%c了。
并没有不需要%c就确定了该结果比c小
但是代码里改回来了
原理正是:
a^b%c=(a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)%c
————最后一个%c是各阶段都%c
之所以能快速幂,是因为是指数式,底都一样大
因为不断a加倍这个操作
而一般的乘数为a[i],一般只能一个一个算
int quick(int a,int b,int c)
{
int ans=1; //记录结果
a=a%c; //预处理,使得a处于c的数据范围之下
while(b!=0)
{
if(b&1) ans=(ans*a)%c; //如果b的二进制位不是0,那么我们的结果是要参与运算的
b>>=1; //二进制的移位操作,相当于每次除以2,用二进制看,就是我们不断的遍历b的二进制位
a=(a*a)%c; //不断的加倍
}
return ans;
}
把指数给二进制了,log2b复杂度,比线性地一个个看高效
举个例子:
2^3%5,应该是3
轮数 | a | b(二进制) | c | ans |
---|---|---|---|---|
0 | 2 | 3(11) | 5 | 1 |
1 | 4 | 1(1) | 5 | 2 |
2 | 1 | 0(0) | 5 | 3 |
就理解了叭~
注意,快速幂也可不取余,就是acm中不取余容易溢出而已。只要去了%c就是完完全全的快速求幂了
等下明天研究一下:
1.非指数是否有快速幂
2.矩阵快速幂
3.最后再看看没有出场的
Miller-Rabin 素性检验算法
一天写不完一个题解的就是本蒟蒻了。。。
但是我学的慢,学的也透也牢
加油,相信自己!!~~
第二天…
昨天忘记给代码了,快速幂的
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
//#include<iostream>
//#include<algorithm>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define _forplus(i,a,b) for(register int i=(a); i<=(b); i++)
#define _forsub(i,a,b) for(register int i=(a); i>=(b); i--)
ll quick(ll a,int b,ll c){
int res=1;
a%=c;
while(b){
if(b&1)res=(res*a)%c;
b>>=1;
a=a*a%c;
}
return res;
}//运算a^b%c
int main(){
int a=32,b=23,c=45;
printf("%lld",quick(a,b,c));
return 0;
}
接下来继续~~~
- 非指数是否有快速幂
应该是没有的,因为正是每一个乘数都相等,才能将a不断扩倍
比如a!,可以并为三个三个乘,简化一点点,但是还是线性的
而
a^1+a^2+...+a^n,和更一般的等比数列
这样的因为它等于a(a^n-1)/(a-1)
因此等比数列是可以化为快速幂的
- 矩阵快速幂
所谓的矩阵快速幂,其实就是重载乘法为矩阵的乘法
根据
A^n=(A)^f1 *(A^2)^f2 *...*(A^m)^fm
其中fi为0或1,分别表示不会分解出该底数和会分解成该底数
n的二进制就是(fm...f2f1)
矩阵的乘法
传矩阵的基本方法
(因为这是之后加的,可能有点突兀)
固定的维数,外面
a[2][2]
里面也
a[2][2]
不妨外面
a[N][N]
里面
a[N][N]
N是最大限度
可能是最简单的方法
各个维数不固定的二维数组怎么传入函数
代码实现个
发现在传二维数组上出了个问题
void mul(int **a,int al,int aw,int **b,int bl,int bw,int **s,int&sl,int&sw)
报错了
[Error] cannot convert 'int (*)[3]' to 'int**' for argument '1' to 'void mul(int**, int, int, int**, int, int, int**, int&, int&)'
因为从实参传递来的是数组的起始地址,如果在形参中不说明列数,编译器将无法定位元素的的位置。
- ——对于各个维数不固定的二维数组
我们完全可以不把它当作一个二维数组,而是把它当作一个普通的指 针,再另外加上两个参数指明各个维数,然后我们为二维数组手工寻址
在转变后的函数中,array[i][j]这样的式子是不对的,因为编译器不能正确的为它寻址,所以我们需要模仿编译器的行为把array[i][j]这样的式子手工转变为: (( int )a+ n*i + j);
但是有没有更简单直接的办法?
参考简书大佬
- 方法3:把参数声明为指向指针的指针
这里要注意的是指针的指针,和二维数组的差异;二维数组的地址是连续的,所有成员按顺序排序;而指针的指针只要求指针地址连续,而不要求指针的指针地址连续。
然后作为实参传递时,也不能直接使用a传递,因为类型不匹配,必须定义新的变量p,然后把a的值赋给p,再传递给foo函数。
作者:CodingCode
链接:https://www.jianshu.com/p/d7f2afe08f41
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include <stdio.h>
void foo(int **a, int m, int n) {
int i = 1;
int j = 1;
printf("a[%d][%d]=%d\n", i, j, a[i][j]);
}
int main() {
int a[2][3] = {
{1,2,3},
{4,5,6}
};
int * p[3];
p[0] = a[0];
p[1] = a[1];
p[2] = a[2];
foo(p, 2, 3);
}
优点:
函数内部可以使用二维数组形式
缺点:
外部要处理出一个指向各指针的指针,
可以写一个得到该指针的函数,定义二维数组仍不变
以上共同缺点:
不便于处理多维,可以写一个循环函数,得到
3.启发: 宏定义,按计算机原理?
#define a[i][k] ...
问题1.无法如此定义
问题2.无法得知n
还不如写一个inline 函数
对于1,写inline函数的方法
浮现出另一个语法问题
二维数组初始化问题
int b[3][3]={{},{b[1][1]=1,2},{b[2][1]=2,3}};
这种写法是没有的!
一维数组也记错了,
是这样的
- 借助于 C99 新增的元素指示符(element designator),可以把初始化器关联到特定的元素。当需要把特定的元素与初始化器关联时,将其索引值放在方括号内。换句话说,数组元素的元素修饰符一般格式如下:
[常量表达式]
索引值必须是整数常量表达式,在下面的示例中,元素指示符是 [A_SIZE/2]:
#define A_SIZE 20
int a[A_SIZE] = { 1, 2, [A_SIZE/2] = 1, 2 };
该数组在定义时把元素 a[0] 和 a[10] 初始化为 1,把元素 a[1] 和 a[11] 初始化为 2。该数组的所有其他元素都被初始化为 0。在这个例子中,没有元素指示符的初始化器会被关联到前一个初始化元素的下一个元素。
!!!原来是这样的
C99能给特定元素初始化,而C++没有。
确实换成.c就可以了,我是C语言入门的,所以不知道C++不能用
int a[4]={[2]=1,3};
可以
但是
int a[4]={a[2]=1,3};
错误,c语言有报错
C语言也支持
#include <stdio.h>
int main ()
{
int a[4][4]={[2]=1,3};
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
printf("%d ",a[i][j]);
}
printf("\n");
}
return(0);
}
和
#include <stdio.h>
int main ()
{
int a[4][4]={[2][1]=1,3};
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
printf("%d ",a[i][j]);
}
printf("\n");
}
return(0);
}
都可以
不过以后使用时
并不是这么初始化的
所以仍然可以以1开始算
回到矩阵乘法:
- 方法1.普通指针
注意点:传入普通指针,传a[0]。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
//#include<iostream>
//#include<algorithm>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define _forplus(i,a,b) for(register int i=(a); i<=(b); i++)
#define _forsub(i,a,b) for(register int i=(a); i>=(b); i--)
int a[3][3]={{},{0,1,2},{0,3,4}};
int b[3][3]={{},{0,1,2},{0,2,3}};
int s[3][3];
//处理二维数组
inline int g(int i,int j,int n){
//由于11开头,所以n++
return i*(n+1)+j;
}
void mul(int *a,int al,int aw,int *b,int bl,int bw,int *s,int&sl,int&sw)
{
if(aw!=bl){
printf("出错了\n");return;
}
//mem(s,0);//这里mem不管用了,s不是那么大 因为退化为指针的s永远是8db(64位下)
sl=al,sw=bw;
_forplus(i,1,al){
_forplus(j,1,bw){
s[g(i,j,sw)]=0;//这里
_forplus(k,1,aw){
s[g(i,j,sw)]+=a[g(i,k,aw)]*b[g(k,j,bw)];
//printf("a[%d][%d]=%d\nb[%d][%d]=%d\n",i,k,a[g(i,k,aw)],k,j,g(k,j,bw));
}
//printf("s[%d][%d]=%d\n",i,j,s[g(i,j,sw)]);
}
}
} //矩阵乘法 a*b=s
void printm(int*s,int sl,int sw){
_forplus(i,1,sl){
_forplus(j,1,sw){
printf("%4d",s[g(i,j,sw)]);
}
printf("\n");
}
}//输出矩阵
int main(){
int sl,sw;
printm(a[0],2,2);
printm(b[0],2,2);
mul(a[0],2,2,b[0],2,2,s[0],sl,sw);
printm(s[0],sl,sw);
return 0;
}
- 方法2:传双指针
注意点:每个二维数组,分配了一个新的数组,元素是指针,指向a中各维
之所以使用宏,是为了避开麻烦的函数传a报错,也很简便,因为只需要找各a[i]指针即可
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
//#include<iostream>
//#include<algorithm>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define _forplus(i,a,b) for(register int i=(a); i<=(b); i++)
#define _forsub(i,a,b) for(register int i=(a); i>=(b); i--)
#define gp(p,a,l) _forplus(i,1,(l))(p)[i]=(a)[i];
int a[3][3]={{},{0,1,2},{0,3,4}};int *pa[3];
int b[3][3]={{},{0,1,2},{0,2,3}};int *pb[3];
int s[3][3];int *ps[3];
void mul(int **a,int al,int aw,int **b,int bl,int bw,int **s,int&sl,int&sw)
{
if(aw!=bl){
printf("出错了\n");return;
}
//mem(s,0);//不行,因为退化为指针的s永远是8db(64位下)
sl=al,sw=bw;
_forplus(i,1,al){
_forplus(j,1,bw){
s[i][j]=0;
_forplus(k,1,aw){
s[i][j]+=a[i][k]*b[k][j];
//printf("a[%d][%d]=%d\nb[%d][%d]=%d\n",i,k,a[g(i,k,aw)],k,j,g(k,j,bw));
}
//printf("s[%d][%d]=%d\n",i,j,s[g(i,j,sw)]);
}
}
} //矩阵乘法 a*b=s
void printm(int**s,int sl,int sw){
_forplus(i,1,sl){
_forplus(j,1,sw){
printf("%4d",s[i][j]);
}
printf("\n");
}
}//输出矩阵
int main(){
int sl,sw;
gp(pa,a,2);
gp(pb,b,2);
gp(ps,s,2);
printm(pa,2,2);
printm(pb,2,2);
mul(pa,2,2,pb,2,2,ps,sl,sw);
printm(ps,sl,sw);
return 0;
}
继续矩阵快速幂
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
//#include<iostream>
//#include<algorithm>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define _forplus(i,a,b) for(register int i=(a); i<=(b); i++)
#define _forsub(i,a,b) for(register int i=(a); i>=(b); i--)
int a[3][3]={{},{0,1,2},{0,3,4}};
int b[3][3]={{},{0,1,2},{0,2,3}};
int s[3][3];
int te[3][3];
//处理二维数组
inline int g(int i,int j,int n){
//由于11开头,所以n++
return i*(n+1)+j;
}
void mul(int *a,int al,int aw,int *b,int bl,int bw,int *s,int&sl,int&sw)
{
if(aw!=bl){
printf("出错了\n");return;
}
sl=al,sw=bw;
_forplus(i,1,al){
_forplus(j,1,bw){
s[g(i,j,sw)]=0;
_forplus(k,1,aw){
s[g(i,j,sw)]+=a[g(i,k,aw)]*b[g(k,j,bw)];
}
}
}
} //矩阵乘法 a*b=s
void quickm(int * a,int al,int b,int *s){
while(b){
if(b&1){
mul((int*)s,al,al,(int*)a,al,al,(int*)te,al,al);
memcpy(s,te,sizeof(int)*(al+1)*(al+1));
}
b>>=1;
mul((int*)a,al,al,(int*)a,al,al,(int*)te,al,al);
memcpy(a,te,sizeof(int)*(al+1)*(al+1));
}
}//运算a^b存于s ,s开始是单位矩阵
void printm(int*s,int sl,int sw){
_forplus(i,1,sl){
_forplus(j,1,sw){
printf("%20d",s[g(i,j,sw)]);
}
printf("\n");
}
}//输出矩阵
void em(int *s,int sl){
_forplus(i,1,sl){
s[g(i,i,sl)]=1;
}
}//让s是单位矩阵
int main(){
printm(a[0],2,2);
int b=10;
//要初始化s为单位矩阵
mem(s,0);//也不可少
em(s[0],2);
quickm(a[0],2,b,s[0]);
printm(s[0],2,2);
return 0;
}
最大的麻烦就是类型不匹配
开始改来改去的
后来发现,要不忘初心
该怎么样,就强制转换,就不会编译错误了。。。
哎无奈啊
发现错误
由矩阵的类型关系启发:
因为退化为指针的s永远是8db(64位下)
所以之前的mem(s)全要改,改成一个一个置0!!
修改错误ing…