算法进阶指南0.1 位运算

常见的位运算

&、或 |、非、异或^
左移 >> 、右移<<

补码

首先我们来简单介绍一下 原码、反码、补码的概念:

1. 原码

原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
.

 [+1]= 0000 0001 
 [-1]= 1000 0001

2. 反码

反码的表示方法是:

  1. 正数的反码是其本身
  2. 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+1] = [00000001]= [00000001][-1] = [10000001]= [11111110]

可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.

3. 补码

补码的表示方法是:

  1. 正数的补码就是其本身
  2. 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+1] = [00000001]= [00000001]= [00000001][-1] = [10000001]= [11111110]= [11111111]

按位取反运算符~

二进制数在内存中以补码的形式存储。

按位取反:二进制每一位取反,0变1,1变0。

~9的计算步骤:

转二进制:0 1001

计算补码:0 1001

按位取反:1 0110

转为原码:

按位取反:1 1001

末位加一:1 1010

符号位为1是负数,即-10

求负数运算:

-x = ~x + 1

如:

1 表示为 00000…01. , ~1 表示为:11111…10, ~1 +1表示为 :11111…11

 00000...01 + 11111...11 = 00000..00

例题

89. a^b

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

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

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

数据范围:0a,b,p1090≤a,b,p≤10^9

首先我们想到用循环暴力法:

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String args[]) throws Exception {
        Scanner cin=new Scanner(System.in);
        int a = cin.nextInt();
        int b = cin.nextInt();
        int p = cin.nextInt();
        int res = 1;
        
        while (b > 0) {
            res = res * a;
            res %= mod;
        }
        System.out.println(res);
    }
}

但是我们可以看到a, b 的取值范围为 0≤a,b,p≤109, 所以循环暴力法无疑是超时的。这就需要我们用到快速幂的思想。可将时间复杂度降低至 O(log2n)。以下从 “二分法” 和 “二进制” 两个角度解析快速幂法。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
快速幂模版

int qmi(int m, int k, int p)
{
    int res = 1 % p, t = m;
    while (k)
    {
        if (k&1) res = res * t % p;
        t = t * t % p;
        k >>= 1;
    }
    return res;
}
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String args[]) throws Exception {
        Scanner cin=new Scanner(System.in);
        /*
			res, a 可能会溢出,所以修改为long格式
		*/
        long a = cin.nextInt();
        long b = cin.nextInt();
        int p = cin.nextInt();
        long res = 1;
        
        while (b > 0) {
            if ((b & 1) == 1) {
                res *= a;
                res %= p;
            }
            a *= a;
            a %= p;
            b >>= 1;
        }
        res %= p;
        System.out.println((int)res);
    }
}

例二:64位整数乘法

求 a 乘 b 对 p 取模的值。

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

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

数据范围 : 1a,b,p10181≤a,b,p≤10^{18}

输入样例:
3
4
5
输出样例:

2

这道题我们首先想到的是直接乘,简单方便

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long a = sc.nextLong();
        long b = sc.nextLong();
        long p = sc.nextLong();

        System.out.println(a * b % p);
    }
}

但是我们知道数值类型long信息如下:

	long 二进制位数:64
	包装类:java.lang.Long
	最小值:Long.MIN_VALUE=-9223372036854775808 (-2的63次方)
	最大值:Long.MAX_VALUE=9223372036854775807 (2的63次方-1)

int 范围大约为 29-2^{9}long范围大约为218-2^{18},所以直接计算会导致溢出,这就需要我们使用 a + a + a + … + a的方法,因此我们也可以用快速幂思想:

ab=a(b020+b121+...bn2n)a * b = a*(b_{0}*2^{0}+b_{1}*2^{1}+...b_{n}*2^{n})

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long a = sc.nextLong();
        long b = sc.nextLong();
        long p = sc.nextLong();
        long res = 0;
        
        while (b > 0) {
            if ((b & 1) == 1) {
                res = (res + a) % p;
            }
            b >>= 1;
            a = (a + a) % p;
        }
        
        System.out.println(res);
    }
}

参考文章:

原码、反码、补码

按位取反

发布了51 篇原创文章 · 获赞 33 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览