数论——第一周任务总结(快速幂和矩阵快速幂)

3 篇文章 0 订阅
2 篇文章 0 订阅

纸质周任务

纸质周任务都写上是不可能的,这辈子都不可能的

三目运算符

题干:
以下程序输出结果是____
#include <stdio.h>
int main(){
int a = 3, b =3,C=1;
c = 5? a++: b–;
printf("%d\n", c);
return 0;
}

题解:

  1. 首先三目运算符的含义是a?b :c代表为当a成立的结果为1时将b值赋给a,否则就将c赋值给a。
  2. 在此处c=5是一个赋值的操作,因而返回值理所当然是1,而a++(a此时的值是3)给了因而是答案为3

逗号运算符

题干
4、下面程序段运行的结果是___
#include<stdio.h>
int main()
{
int x,y;
for(y = 1; y < 10;)
{
y=((x=3*y,X+1),x- 1);
}
printf(“x=%d, y=%d\n”,x,y);
return 0;
}
题解

  1. 在同一个小括号之内逗号运算的整个表达式的值是最后一个表达式的值。
  2. 因为在主函数之中定义x,y,因而这两个未知数是随机赋值(在主函数外定义没有初始化,自动为0,结构体也不例外)

位运算

题干
在这里插入图片描述
讲述位运算之前首先引入原码,补码,反码三个关于机器语言的知识
以下为本人薄浅的认识,如有错误请指出
原码:对于一个数字可以转化二进制的码,最高位是代表这个数的正负性,其余位用来表示这个数的的大小。
在这里插入图片描述

反码:正数的反码是原码其本身,而负数的反码是其原码的基础之上,符号位不变,其余位取反。
在这里插入图片描述
(反码的出现是为了解决在计算机之中只会加法运算的问题 )

补码:正数的是原码其本身,而负数的补码是在其原码的基础之上,符号位不变,其余各位取反,最后在加上1。
因为在计算机之中只有加法运算,因而可通过反码相加的形式来完成减法运算,而补码的出现是为了解决反码相加之中出现+0和-0的情况
在这里插入图片描述

ps:在计算机之中,储存数字和进行运算都是用补码进行


进入正题:
位运算一共有6种:

  • 按位与(&)
    参与运算的两个数换成二进制,进行与运算,只有当相应为是1时,该为才是1,否则则为0。

     例子:10&-10
     		0000 0000 0000 1010
     		1111 1111 1111 0110
     		—————————————————————
     		0000 0000 0000 0010
    

运用

  1. (假设有一个数字a)那么可以通过if(( a & 1 ) == 0)来代替if((a%2)==0)通过这个方 法判断一个数知否是偶数
  2. 和以上原理差不多,当 a相对16进行取余操作时,可以用a&15来做出来(但是只能对2^n数取余才能这样操作
  • 按位或(|)
    参与运算的两个数,换算成二进制(补码)相应位都是0才为0,否则为1

    例子:10|-10
    0000 0000 0000 1010
    1111 1111 1111 0110
    —————————————————————
    1111 1111 1111 1110
    应用
    k|1等价于k+1(k为偶数的时候)

  • 按位异或(^)
    参与运算的两个数,换算成二进制(补码)相应位不同才为1,否则为0
    运用:(假设有两个数a,b)

    1. a ^ 0 = a( 任何数和0异或都是它本身。) ; a ^ a = 0
    2. 交换两个数的值:a = a ^ b ; b = a ^ b ; b = a ^ b
    3. 如果说a ^ b ^ c = 0
      那么我们可以推出a ^ b =c 和 a ^ c = b 和 b ^ c = a
    4. 可以一直反转状态:0 ^ 1 =1; 1 ^ 1 = 0 ; 0 ^ 1 = 1;(一直和1异或来反转)
    5. 如果说在一串数字中其中其他数字出现次数都是偶数,只有一个数字出现一次,找出那个数。
int a[7]={1,2,2,1,3,4,3};
int num=0;  // 任何数与0异或都是其本身
for(int i=0;i<7;i++){
      num=num^a[i];
}
return num;
  • 取反(~)
    全部取反,包括符号位(与负数的原码转成反码不同,负数的原码转成反码符号位不需要取反)
    运用:取数字a的相反数:~a+1
  • 左移(<<)
    参加运算的两个数,换算为二进制(0、1)后,进行左移运算,用来将一个数各二进制位全部向左移动若干位。多的去掉,缺的用0补上
    会发现左移n位,是对a这个数进行了 * (2^n)
  • 右移(>>)
    参加运算的两个数,换算为二进制(0、1)后,进行右移运算,用来将一个数各二进制位全部向右移动若干位。考虑到符号的
    会发现右移n位,是对a这个数进行了/(2^n)
    正数是进行了/( 2 ^ n)操作,负的奇数除以2^n,再减1

    运用:取数字a的绝对值a>>31?a:(~a+1)

快速幂

首先先用2^6(别人当时用这个例子,我也懒得想其他例子了)进行举例子,用快速幂而不用pow()函数的很大部分原因是pow类似简单的用循环进行相乘操作,这样复杂度太高了,为了适应!我们(我们个蛋,我当时想都没想到)使用快速幂。

#include <stdio.h>
typedef long long ll;

ll binarypow(ll a,ll b)
{    //在这里a为2,b为 6
	int temp = a;
	int ans = 1;
	while(b)
	{
		if(b&1)
			ans *= temp;
		temp *= temp;
		b >>= 1;
	}
	return ans;
}
  1. 首先2^6可以拆分成(2 ^3 )* (2 ^3) 次数为偶数时候
  2. 2^ 3 可以拆分成 2* 2^ 2 次数为奇数时候
  3. 周而复始直到次数为1时返回 1
    这样的复杂度就只有 O(log2n)

题干
在这里插入图片描述
在这里插入图片描述
输入和输出
在这里插入图片描述
此处我们对这个程序进行打表就会发现它的规律,不打表也可以那就是找规律,根据同余的两条规律可以得出这就是一个二次展开式,而二次展开式最后结果就是2^ n。

我们可以对我们刚刚的代码进行一个小小的修改:

#include<stdio.h>
typedef long long ll;
ll binarypow(ll a,ll b,ll m)
	{
		if(b==0)
			return 1;
		else if(b%2==1)
			return a*binarypow(a,b-1,m)%m;
		else 
		{
			ll num =binarypow(a,b/2,m)%m;
			return num*num%m;
		}
	}
int main()
{
	ll t,n;
	scanf("%lld",&t);
	while(t--)
	{
		scanf("%lld",&n);
		printf("%lld\n",binarypow(2,n,1e9+7));
	}
return 0;
}

scanf()的各种正则表达式输入

  • scanf("%[\n]",str); 只读\n的意思
  • scanf("%[ ^\n ] ",str);除了\n都读(读到\n前)
    这个方式是可以读取空格的
  • scanf(“%[a-z]”,str);只读字母a-z
  • scanf(“%[a-zA-Z]”);只读字母a-z和A-Z
  • scanf(”%*c%c“,str);跳过第一个遇到的字符
    总结:
    %[ ] 读取指定的字符
    %[ ^ ]不读取指定字符
    %s 读取任意字符
    %6s 读取长度为6 的字符
    %*c 跳过这个字符

矩阵快速幂

先构建矩阵

#include<iostream>
using namespace std;
#define maxn 100000

struct mat{
	int c[maxn][maxn];
}unite;

mat matual(mat a, mat b, int n, int mod)
{
	mat ans ;
	int i, j, k, temp;
	for(i = 0; i < n; i++)
		for(j = 0; j < n; j++)
		{
			temp = 0;
			for(k = 0; k < n; k++)
				temp = (temp + a.c[i][k] * b.c[k][j])%mod;
			ans.c[i][j] = temp;			
		}
	return ans;
}

mat quickpow(mat a, int n, int  mod)
{
	mat tmp = a;
	mat res ;
	for(i = 0; i < n; i++)
		for(j = 0; j < n; j++)
			res.c[i][j] = 1;
	while(n)
	{
		if(n&1)
			res = matual(res, tmp);
		tmp = matual(tmp, tmp)
		n >>= 1;
	}
	return ans;
}

例题

奇异的虫群

传送门
在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
#define mod 1000000007

struct mat{
	ll MAT[2][2];
};

struct
mat mul(mat a, mat b)
{
	mat c;
	for(int i = 0; i < 2; i++)
		for(int z = 0; z < 2; z++)
		{
			c.MAT[i][z] = 0;
			for(int k = 0; k < 2; k++)
				c.MAT[i][z] = (c.MAT[i][z] + a.MAT[i][k] * b.MAT[k][z])%mod;
		}
	return c;
}

mat quick_pow(mat a, ll b)
{
	mat ans = {1,0,0,1};
	while(b)
	{
		if(b&1)
			ans = mul(a, ans);
		a = mul(a,a);
		b >>= 1;
	}
	return ans ;
}

int main()
{
	mat a = {1,1,1,0};
	mat ans;
	ll t;
	cin >> t; 
	t--;   // 1时刻是1,1,不用算
	ans = quick_pow(a,t);
	ll ans1 = (ans.MAT[0][0] + ans.MAT[0][1])%mod;
	cout << ans1;
	
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值