//点击后出现烟花效果

位运算在算法中的应用

快速幂

问题:

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

分析:

我们可以将 \(b\) 转换成二进制:

\[b = c_02^0+c_12^1+c_22^2+...+c_{k-1}2^{k-1} \]

其中 \(c_n\) 的取值为 \(0\) 或者 \(1\)

那么有:

\[a^b = a^{c_02^0+c_12^1+c_22^2+...+c_{k-1}2^{k-1}} \]

\[a^b = a^{c_02^0}a^{c_12^1}a^{c_22^2}...a^{c_{k-1}2^{k-1}} \]

并且我们知道

\[a^{2^{n}} = (a^{2^{n-1}})^2 \]

故我们只需要递推即可得出每个乘积项,当 \(c_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,\ p\) 计算 \(a\times b\mod p\) \((1 \le a,b,p \le 10^{18})\)

分析:

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

\[b = c_02^0+c_12^1+c_22^2+...+c_{k-1}2^{k-1} \]

然后有

\[a\times b = c_0a2^0+c_1a2^1+c_2a2^2+...+c_{k-1}a2^{k-1} \]

又因为

\[a2^n = (a2^{n-1})\times 2 \]

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

同样当 \(c_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题库


二进制状态压缩

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

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

操作 运算
取出 \(n\) 的第 \(k\) (n>>k) & 1
取出 \(n\) 的第 \(0\) ~ \(k-1\) 位(后 \(k\) 位) n & ((1<<k)-1)
将第 \(k\) 位取反 n xor (1<<k)
将第 \(k\) 位赋值 \(1\) n | (1<<k)
将第 \(k\) 位赋值 \(0\) n & (~(1<<k))

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

例题:

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


成对变换

对于非负整数 \(n\) ,我们发现:

\(n\) 为奇数时,\(n\ xor\ 1 = n-1\)

\(n\) 为偶数时,\(n\ xor\ 1 = n+1\)

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

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


lowbit运算

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

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

所以有

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

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

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

这里求对数可以预处理,建立一个长度为 \(37\) 的数组 \(H\) ,令 \(H[2^k \mod 37] = k\) ,有一个小技巧,\(\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也是树状数组的一个基本运算

posted @ 2024-01-26 23:47  JunieXD  阅读(44)  评论(0)    收藏  举报