0x00 基本算法

0x01 位运算

前言

在我刚开始刷题的时候,觉得位运算很简单,只不过是一些与或非等等的一些操作罢了。现在看来,是我目光短浅,见识少的缘故,导致我无法窥得位运算的精妙之处。

位运算有四种基本操作:

异或
and, &or, |not, ~xor, ^

此外,在 m m m位二进制数中,通常称最低位为第0位,从右到左以此类推,最高位为第 m − 1 m-1 m1

补码

以C++的 int 和 unsigned int 类型为例。

  • 32位无符号整数 unsigned int:原码与补码相同。
  • 32位有符号整数 int:最高位为符号位,0表示非负数,1表示负数。最高位为0时,直接可以当作32位二进制数,最高位为1时,需要将其按位取反并+1,得到负数的值。
32位补码表示无符号整数有符号整数
000000…00000000
011111…11111121474836472147483647
100000…0000002147483648-2147483648
111111…1111114294967295-1

移位运算

左移

二进制下每一位数字同时向左移动,低位填充0,高位越界后舍弃。
1 ≪ n = 2 n , n ≪ 1 = 2 n 1 \ll n = 2^n, n \ll 1= 2n 1n=2n,n1=2n

算术右移

二进制补码表示下每一位数字同时向右移动,高位以符号位填充,低位越界后舍弃。
n ≫ 1 = ⌊ n 2.0 ⌋ n \gg 1 = \lfloor \frac{n}{2.0} \rfloor n1=2.0n

逻辑右移

在二进制补码表示下每一位数字同时向右移动,高位以0填充,地位越界后舍弃。

例题

1、 a b a^b ab

求a的b次方对p取模的值,其中 1 ≤ a , b , p ≤ 10 9 1\le a,b,p \le 10^9 1a,b,p109

思路:我们知道,每一个正整数都可以通过二进制数来表示。假设b在二进制表示下有k位,因此, b b b可以表示为:
b = c k − 1 2 k − 1 + c k − 2 2 k − 2 + . . . + c 0 2 0 b=c_{k-1}2^{k-1} + c_{k-2}2^{k-2} +...+c_02^0 b=ck12k1+ck22k2+...+c020
其中, c i ( 0 ≤ i ≤ k ) c_i(0 \le i \le k) ci(0ik)表示 b b b在二进制表示下第 i i i位的数字,取值为0或者1。

因此:
a b = a c k − 1 ∗ 2 k − 1 ∗ a c k − 2 ∗ 2 k − 2 ∗ . . . ∗ a c 0 ∗ 2 0 a^b =a^{c_{k-1}*2^{k-1}}*a^{c_{k-2}*2^{k-2}}*...*a^{c_0*2^0} ab=ack12k1ack22k2...ac020
因为 k = ⌈ l o g 2 ( b + 1 ) ⌉ k=\lceil log_2(b+1) \rceil k=log2(b+1)⌉,所以上式乘积项最多为 ⌈ l o g 2 ( b + 1 ) ⌉ \lceil log_2(b+1) \rceil log2(b+1)⌉个。又因为:
a 2 i = ( a 2 i − 1 ) 2 a^{2^i}=(a^{2^{i-1}})^2 a2i=(a2i1)2
所以我们可以通过k次递推求出每个乘积项。

代码:


int power(int a, int b, int p)
{
    int ans = 1 % p;
    for(; b > 0; b >>= 1){
        // 如果该位为1
        if(b & 1){
            ans = (long long)ans * a % p;  // 通过long long强制转换,防止越界
        }
        a = (long long)a * a % p;
    }
    return ans;
}

算法时间复杂度: O ( l o g 2 b ) O(log_2b) O(log2b)

如果 1 ≤ a , b , c ≤ 10 18 1 \le a,b,c \le 10^{18} 1a,b,c1018,那么 long long 的强制类型转换不再可行,并且不存在一个可供强制类型转换的128位整型类型。因此,我们需要一些特殊的处理办法。

2、64位整数乘法

求a乘b对p取模的值,其中 1 ≤ a , b , c ≤ 10 18 1 \le a,b,c \le 10^{18} 1a,b,c1018

思路:采用快速幂的思想。把整数 b 用二进制表示,即 b = c k − 1 2 k − 1 + c k − 2 2 k − 2 + . . . + c 0 2 0 b=c_{k-1}2^{k-1}+c_{k-2}2^{k-2}+...+c_02^0 b=ck12k1+ck22k2+...+c020,那么 a ∗ b = c k − 1 ∗ a ∗ 2 k − 1 + c k − 2 ∗ a ∗ 2 k − 2 + . . . + c 0 ∗ a ∗ 2 0 a*b=c_{k-1}*a*2^{k-1}+c_{k-2}*a*2^{k-2}+...+c_0*a*2^0 ab=ck1a2k1+ck2a2k2+...+c0a20

因为 a ∗ 2 i = ( a ∗ 2 i − 1 ) ∗ 2 a*2^{i}=(a*2^{i-1})*2 a2i=(a2i1)2,如果已经求出 a ∗ 2 i − 1 a*2^{i-1} a2i1%p,那么预算过程中每一步的结果都不会超过 2 ∗ 10 18 2*10^{18} 21018,仍然在64位整数 long long 的表示范围内。所以可以通过 k 次递推求出每个乘积项。

代码:

long long mul_64(long long a, long long b, long long p)
{
    long long ans = 0;
    for(; b > 0; b >>= 1){
        if(b & 1){
            ans = (ans + a) % p;
        }
        a = a * 2 % p;
    }
}

时间复杂度: O ( l o g 2 b ) O(log_2b) O(log2b)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值