08. 位运算&快速幂

08. 位运算&快速幂

位运算

对于计算机而言,只能处理二进制数据,所以无论多么高级的语言最后都会被计算机转为二进制数据 01 的格式。
比如:十进制的 10,我们会将它记为 10D,D 表示 DEC

我们知道 \(10 =8+2 = 2^3 + 2^1 = 1*2^3+0*2^2+1*2^1+0*2^0\)
所以可以转为二进制:1010B
如果我们使用2Byte,也就是8bit 来储存这个数据,由于是正数,符号位为0,所以10D的二进制原码为:0000 1010B。

学习位运算前,需要掌握二进制,那么我们这里就将进制转换在复习一下

常用进制:2,8,10,16
计算公式:结果 = sum(基数*位数权重)

二进制 bin,Binary :1001B  = 8+1 =9D
八进制 oct,Octal  :1005O  = 1*8^3+5*8^0=8^3+5
十进制 dec,Decimal:1289D  = 1289D
十六进制 hex,Hexadecimal:0x119f = 1*16^3+1*16^2+9*16^1 +15*16^0
#include<iostream>
#include<iomanip>
using namespace std;
int main(){
    int n; cin>>n;      // 10
    cout<<oct<<n<<endl; // 12,以八进制格式输出
    cout<<dec<<n<<endl; // 10,以十进制格式输出
    cout<<hex<<n<<endl; // a  ,以十六进制格式输出
    cout<<setiosflags(ios::uppercase)<<hex<<n<<endl; // A
    return 0;
}
& : 按位与,同真为真,其余为假
| : 按位或,同假为假,其余为真
^ : 异或运算,相异为真,相同为假
~ : 取反,真假颠倒
<<: 左移:按二进制把数字向左移动对应位数,高位移出(舍弃),低位的空位补0。
>>: 右移:按二进制把数字向右移动对应位数,低位移出(舍弃),高位的空位补符号位,即正数补0,负数补1
>>左移运算符:num << n; 相当于num乘以2的n次方(低位补0)
>>左移运算符:num >> n; 相当于num除以2的n次方
10D = 8+2 --> 1010B<<2 = 101000B = 32+8=40 = 10*2^2
10D = 8+2 --> 1010B>>2 = 0010B = 2 = 10/(2^2)=2

原码,反码,补码是机器存储一个具体数字的编码方式
原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值

比如如果是8位二进制:
+2 的原码 = 0000 0010
-2 的原码 = 1000 0010
第一位为符号位,所以8位二进制取值范围为[1111 1111, 0111 1111] 也就是[-127, 127] 

反码:正数的反码就是原码,负数的反码是在原码的基础上,符号位不变,其余位取反

+2的反码 = 0000 0010
-2的反码 = 1111 1101

补码:正数的补码就是原码,负数的补码是在反码的基础上+1

+2的反码 = 0000 0010
-2的反码 = 1111 1110
总结:
    正数的原码,反码,补码都是原数的二进制;
    负数的原码就是符号位为1,其余位按正数计算
    负数的反码就是符号位为1,其余位按正数取反
    负数的补码就是符号位为1,其余位按正数取反后+1

原码:10D=0000 1010B, -10D=1000 1010B
反码:10D=0000 1010B, -10D=1111 0101B
补码:10D=0000 1010B, -10D=1111 0110B

位运算的一些使用

我们知道一个数的二进制组成一定是 01 格式,那么
对于偶数而言:其末位一定为 0;// 如 8D = 1000B
对于奇数而言:其末位一定为 1;// 如 7D = 0111B

【例】 输入一个数,判断它是否是2的整数幂,如果是就输出Yes,如果不是就输出No。

方法1:判断n是否是2的整数幂,最简单的办法就是在数字大于0且能被2整除时,就反复除以2。
退出循环时,若商不是1,n就不是2的整数幂,否则就是。

#include<iostream>
using namespace std;
int main() {
    int n; cin>>n;
    while(n>0 && n%2==0)  n>>=1;
    if(n!=1) cout<<"No";
    else cout<<"Yes";
    return  0 ;
}

方法2:利用位运算进行计算。
举例:8D = 1*2^3 = 1000B,如果是 2^k 次,则一定只有首位为 1,其余位为 0;
则将其原码-1,应当出现全部按位取反的情况,则此时将二者进行按位与,会得到 0。

#include<iostream>
using namespace std;
int main() {
    int n; cin>>n;
    if(n&(n-1))  cout<<"No";
    else cout<<"Yes";
    return  0 ;
}

【例】不及格人数
学习了一段C++程序设计后,老师组织了验收考核。
考核分为笔试和上机两种形式,每位同学也便有了两个成绩,
现在老师想知道,参加考核的n人中有多少人只有一个成绩不及格(每场考核满分为100分,不低于60算及格)。

#include <iostream>
using namespace std;
int main() {
    double x,y,n,ans=0;  cin>>n;
    for(int i=1; i<=n; i++) {
        cin>>x>>y;
        if(x<60 ^ y<60) ans++;
    }
    cout<<ans;
    return 0;
}

【例】街灯
在一条笔直的街道上,有无数的街灯,每盏灯有自己的独立开关。
为了检验灯的质量,管理员想出了一个有趣的办法,找若干人按顺序一个一个地从街道的一侧进入,每个人看到亮着的灯就熄灭,直到看到第一盏关着的灯,将其点亮,任务完成。如果所有灯都质量完好,且初始时都关闭,那么第m个人走过后,有多少灯被点亮过?
输入样例:4
输出样例:3

#include<iostream>
using namespace std;
int main() {
    int n,num=1; cin>>n;
    n>>=1;
    while(n!=0) {
        num++;
        n>>=1;
    }
    cout<<num;
    return 0;
}

P1226 【模板】快速幂||取余运算

给你三个整数 a,b,p,求 a^b mod p。

输入格式:一行三个整数,分别代表 a,b,p;
输出格式:一行一个字符串 a^b mod p=s,a,b,p 分别为题目给定的值, s 为运算结果。

输入样例:2 10 9
输出样例:2^10 mod 9=7

对于 100% 的数据,保证 0 ≤ a,b < 2^31, a+b > 0, 2 ≤ p < 2^31

分析:顾名思义,快速幂就是快速计算底数的 n 次幂。
其时间复杂度为O(logN),与朴素的O(N)相比效率有了极大的提高。
快速幂有两种理解方式:二分递归和二进制,但是其本质都是二进制。

二分递归证明如下:

当 n 为偶数的时候:\(a^{n} = a^{n/2} * a^{n/2}\)
当 n 为奇数的时候:\(a^n = a^{n/2} * a^{n/2} * a\) ;

注意:快速幂的运算结果是呈几何倍数的增加,所以很容易爆范围,故而开 long long
经常对于这样的题目,会说对某个数取模,这里我们可以记住以下几个取余运算相关性质:

(a + b) % p = (a % p + b % p) % p
(a - b) % p = (a % p - b % p) % p
(a * b) % p = (a % p * b % p) % p
除法不满足

这里我们选择第一个进行证明。

\[证明:(a+b) \% p = (a \% p + b \% p) \% p; \\ \quad \\ 令:a= k1 * p + r1 , \quad b= k2 * p + r2, \\ \quad \\ \begin{aligned} (a+b)\%p &= (k1*p+r1 + k2*p+r2) \% p \\ \\ &= ((k1+k2)*p + r1 + r2) \% p \\ \\ &= (r1+r2) \% p \end{aligned} \\ \quad \\ \begin{aligned} (a\%p + b\%p) \% p &= ((k1*p + r1)\%p + (k2*p + r2)\%p) \% p \\ \\ &= (r1+r2) \% p ,原式得证。 \end{aligned} \]

由于减法其实就是加上一个负数,乘法就是多个加法,所以 也可以伪证另外两条性质。

ll halfpow(ll a, ll n){
    if(n==0) return 1;
    ll ans=halfpow(a, n/2);
    ans = ans*ans%mod;
    if(n&1LL) ans=ans*a%mod;
    return ans;
}
  • 进制原理实现快速幂(二分快速幂思想不变,但是从进制原理的角度去理解)

  • \(a^{n} = a^{XB}\)

  • \(a^{11} = a^{8+2+1} = a^{2^{3} +2^{1} + 2^{0}} = a^{1011B}\)

发现仅当二进制位为 1 时,才会对其进行累乘,而累乘的数值为该进制位的权值次幂(基数)。

比如:1011,从右向左,仅当第 0 位,第 1 位,第 3 位时才进行累乘计算。

算法流程:
    初始值:ans=1, a=a, X=1011
    如果X的二进制最低位为1,即 lowbit(X)=1,则 ans = ans*a;
    基数一直: a = a*a;

主要在于:a = a*a 的理解
    次数:数值
    0:a    = a^(2^0)
    1:a^2  = a^(2^1)
    2:a^4  = a^(2^2)
    3:a^8  = a^(2^3)
    4:a^16 = a^(2^4)
ll fastpow(ll a, ll n){
    ll ans=1;
    while(n){
        if(n&1LL) ans = ans*a%mod;
        a = a*a%mod;
        n >>= 1; // n/=2;
    }
    return ans;
}

快速幂练习题:https://www.cnblogs.com/hellohebin/p/16294828.html

posted @ 2022-05-25 08:48  HelloHeBin  阅读(412)  评论(0编辑  收藏  举报