基本算法:位运算

89. ab

求 a 的 b 次方对 p 取模的值。

输入格式
三个整数 a,b,p ,在同一行用空格隔开。

输出格式
输出一个整数,表示ab mod p的值。

数据范围
0≤a,b≤109
1≤p≤109
输入样例:

3 2 7

输出样例:

2

思路

因为b数据范围很大,不可能用爆搜解决。这里可以考虑用二进制表示b,从而减少运算

b=ck-12k-1+ck-22k-2+…+c020
`
两个底数相同的数相乘,指数相加,所以我们就是要用2的整次幂把指数 b 加出来。那么如果 b 的二进制表示里第 k 位是1,就加上 2k,也就是乘上 a2^^k,这就是快速幂的思想

(迭代版)

#include<iostream>
using namespace std;
const int N = 1e9 + 10;
typedef long long ll;

int a,b,p;
ll ans;

int main()
{
    scanf("%d%d%d", &a, &b, &p);
    
    ans = 1 % p;  //可以处理p=1的情况
    for(;b;b >>= 1)
    {
        //如果第k位是1,就乘上对应的 a^2^k;
        if(b & 1 ) ans = ans * a % p;  //b当前位是否为1,每次循环mod p是为了防止溢出
        a = (long long )a * a % p;
        //这里是倍增,每次求 a^2^1 %p,a^2^2 %p,a^2^3 %p,…a^2^1 %p,a^2^2 %p,a^2^3 %p,… 的值;
    }
    //两个底数相同的数相乘,指数相加,所以我们就是要用2的整次幂把指数 b 加出来。
    //那么如果 b 的二进制表示里第 k 位是1,就加上 2^k,也就是乘上 a^2^k
    
    /*
    while写法
    
    while(b)
    {
        if(b & 1) ans = ans * a % p ;
        a = (long long ) a * a % p;
        b >>= 1;
    }
    */
    cout << ans <<endl;
    return 0;
}

(递归版)

#include<iostream>
using namespace std;
typedef unsigned long long ull;
ull quick_pow(ull a,ull b,ull p)
{
    if(b==0) return 1%p;
    a%=p;
    ull res=quick_pow(a,b>>1,p);
    if(b&1) return res*res%p*a%p;
    return res*res%p;
}
int main()
{
    int a,b,p;
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin>>a>>b>>p;
    cout<<quick_pow(a,b,p)<<endl;
    return 0;
}

tips:

  1. i>>k&1可以判断i的第k位是否为1
  2. 可以判断奇偶数,i & 1 = 1 >> 奇数

90. 64位整数乘法

求 a 乘 b 对 p 取模的值。

输入格式
第一行输入整数a,第二行输入整数b,第三行输入整数p。

输出格式
输出一个整数,表示a*b mod p的值。

数据范围
1≤a,b,p≤1018
输入样例:

3
4
5

输出样例:

2

思路
这题与上一题不同的是,变量abp都在1018级别,c++最高内置整数类型是64位,不存在一个128位的可供转换的整数类型,所以需要另一种方法解决。

  1. 方法一
    类似于快速幂
    在这里插入图片描述
    a * 2i = (a*2i-1 2 若已求出 a * 2i-1 mod p,则计算 (a * 2i-1) * 2 mod p,则运算过程中每一步的结果都不超过21018,则可以通过k次递推求出每个乘积项。当ci等于1时,把该乘积项累积到答案即可。时间复杂度O(log2b)
#include <iostream>

using namespace std;

typedef unsigned long long ull;

ull a,b,p;
ull ans;

int main()
{
    cin >> a >> b >> p;
    ans = 0;
    for(;b;b >>= 1)
    {
        if(b&1) ans = (ans + a ) % p;  //如果是1,则把乘积项加上,mod p防溢出
        a = a * 2 % p;
    }
    cout << ans << endl;
    return 0;
}

801. 二进制中1的个数

给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。

输入格式
第一行包含整数 n。

第二行包含 n 个整数,表示整个数列。

输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。

数据范围

1≤n≤100000,
0≤数列中元素的值≤109

输入样例:

5
1 2 3 4 5

输出样例:

1 1 2 1 2

原码 补码 反码

  • 原码:是最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值
  • 举例:
    1010 : 最高位为‘1’,表示这是一个负数,其他三位为‘010’,
    即(0*2^2)+(1*2^1)+(0*2^0)=2(‘^’表示幂运算符)
    所以1010表示十进制数(-2)
    同理 0010表示十进制数(2)

    0001+0010=0011 (1+2=3)ok

    0000+1000=1000 (+0+(-0)=-0) ok

    0001+1001=1010 (1+(-1)=-2)no

    由此引出了反码的概念来解决1 + -1 = -2的问题
  • 反码:正数的反码还是等于原码,负数的反码就是他的原码除符号位外,按位取反。
    举例:
    若以带符号位的四位二进制数为例:
    3是正数,反码与原码相同,则可以表示为0011
    -3的原码是1011,符号位保持不变,低三位(011)按位取反得(100)所以-3的反码为1100

    0001(1 )+1110 (- 1)=1111(-7)no  正确答案为0

    1110(-1)+1101(-2)=1011(-4)no   正确答案为-3

    1110(-1)+1100(-3)=1010(-5)no   正确答案为-4

    细心的你可能发现,除了第一个,其他计算错误的结果差值只是相差了1

    由此引出了补码的概念来解决问题
  • 补码:正数的补码等于他的原码, 负数的补码等于反码+1。也等于正数反码+1。
    可能有小伙伴疑惑为什么  0001(1 )+1110 (- 1)=1111(-7)no  正确答案为0
    如果这个+1不是会错误吗??
    哈哈,显然不是 
    机器码的位数在操作系统中是固定位,假设只有4位
    源码 1 : 0001   -1:1001
    补码 1 :0001   -1:1110 + 1 = 1111
    补码相加 1 + -1 = 0001 + 1111 = 10000,因为保留4位,所以最终结果为0000
    nice~~完美

    负数的补码等于反码+1,也等于正数反码+1。
    1.  -1 :1001  -> 1111
    2.  -1 : (1: 0001)的反码 1110 + 1 -> 1111

  • lowbit原理
  • 根据计算机负数表示的特点,如一个数字原码是10001000,他的负数表示形势是补码,就是反码+1,反码是01110111,加一则是01111000,二者按位与得到了1000,就是我们想要的lowbit操作
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值