Junie's Blog

位运算常见技巧

全文共 714预计阅读 3 分钟

快速幂

问题:

给定整数 a, b, pa,\ b,\ p 计算 abmodpa^b\mod p (1a,b,p109)(1 \le a,b,p \le 10^9)

分析:

我们可以将 bb 转换成二进制:

b=c020+c121+c222+...+ck12k1b = c_02^0+c_12^1+c_22^2+...+c_{k-1}2^{k-1}

其中 cnc_n 的取值为 00 或者 11

那么有:

ab=ac020+c121+c222+...+ck12k1a^b = a^{c_02^0+c_12^1+c_22^2+...+c_{k-1}2^{k-1}} ab=ac020ac121ac222...ack12k1a^b = a^{c_02^0}a^{c_12^1}a^{c_22^2}...a^{c_{k-1}2^{k-1}}

并且我们知道

a2n=(a2n1)2a^{2^{n}} = (a^{2^{n-1}})^2

故我们只需要递推即可得出每个乘积项,当 cn=1c_n = 1 时累积到答案中即可。

代码:

int quickPower(int a, int b, int p) {
    int ans = 1 % p;
    while (b) {
        if (b & 1) ans = (long long)ans * a % p;
        b >>= 1;
        a = (long long)a * a % p;
    }
    return ans;
}

例题:

89. a^b - AcWing题库


龟速乘

问题:

给定整数 a, b, pa,\ b,\ p 计算 a×bmodpa\times b\mod p (1a,b,p1018)(1 \le a,b,p \le 10^{18})

分析:

由于long long只能存 9×10189\times 10^{18} 这么大的数,所以我们要想办法让计算不溢出。根据快速幂的思想,我们可以把 bb 分解成二进制

b=c020+c121+c222+...+ck12k1b = c_02^0+c_12^1+c_22^2+...+c_{k-1}2^{k-1}

然后有

a×b=c0a20+c1a21+c2a22+...+ck1a2k1a\times b = c_0a2^0+c_1a2^1+c_2a2^2+...+c_{k-1}a2^{k-1}

又因为

a2n=(a2n1)×2a2^n = (a2^{n-1})\times 2

如果我们得到 a2n1modpa2^{n-1}\mod p ,然后计算 (a2n1)×2modp(a2^{n-1})\times 2 \mod p 可以保证每一步都不溢出

同样当 ci=1c_i = 1 时将乘积项累加到答案中即可

代码:

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

例题:

90. 64位整数乘法 - AcWing题库


二进制状态压缩

将一个长度为 mm 的 bool 数组用一个二进制整数表示并存储。

利用位运算实现原 bool 数组中对应下标元素的存取。

操作运算
取出 nn 的第 kk(n>>k) & 1
取出 nn 的第 00 ~ k1k-1 位(后 kk 位)n & ((1<<k)-1)
将第 kk 位取反n xor (1<<k)
将第 kk 位赋值 11n | (1<<k)
将第 kk 位赋值 00n & (~(1<<k))

位运算的一个重要特点:在二进制表示下不进位,参与位运算的各个二进制位之间是独立无关的

例题:

998. 起床困难综合症 - AcWing题库


成对变换

对于非负整数 nn ,我们发现:

nn 为奇数时,n xor 1=n1n\ xor\ 1 = n-1

nn 为偶数时,n xor 1=n+1n\ xor\ 1 = n+1

因此,0和1、2和3、4和5...都是关于xor 1运算成对变换

常用于邻接表中无向边的存取


lowbit运算

lowbit(n) 定义为非负整数 nn 在二进制表示下“最低位的 11 及其后边的所有 00”构成的数值。

因为在计算机中 [x]=[x] 按位取反+1[-x]_补 = [x]_补\ 按位取反 + 1

所以有

lowbit(n)=n & (n+1)=n & (n)lowbit(n) = n\ \&\ (\sim n+1) = n\ \&\ (-n)

通过 lowbit 运算,我们可以找到整数二进制表示下所有是 11 的位:

迭代计算 n=nlowbit(n)n = n - lowbit(n) 直到 nn00,减掉的数就是所有二进制是 11 的位与后面的 00 构成的数值,然后通过取对数 log2xlog_2x 即可得出位置。

这里求对数可以预处理,建立一个长度为 3737 的数组 HH ,令 H[2kmod37]=kH[2^k \mod 37] = k ,有一个小技巧,k[0,35],2kmod37\forall k \in [0,35], 2^k \mod 37 互不相等。

int H[37];
for (int i=0;i<37;i++) H[(1ll << i) % 37] = i;
while (cin >> n) {
    while (n > 0) {
        cout << H[(n & -n) % 37];
        n -= (n & -n);
    }
}

lowbit也是树状数组的一个基本运算

评论