0X01 位运算笔记

位运算,经常可以用来处理一些数学或动归方面的问题,通常会在数据范围较小的情况下使用。

为方便起见,一个 n 位二进制数从右到左分别为第 0n1 位。

快速幂 (ACW89 a^b)

abp 取模的值。

a,b109,1p109

考虑 b 的二进制。若可以表示为 i2i 的形式,那么 ab 就可以表示成 ai2i

a2i=(a2i1)2,这样可以通过递推的方式求出 a2i。由于 max{i}logb 级别的,所以总时间复杂度为 O(logb)

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

using LL = long long;

int qpow(int a, int b, int p) {
    int ans = 1 % p; // 这里是为了避免 p = 0 的情况出现
    while (b) {
        if (b & 1) ans = (LL)ans * a % p;
        a = (LL)a * a % p;
        b >>= 1;
    }
    return ans;
}

int main() {
    int a, b, p;
    scanf("%d%d%d", &a, &b, &p);
    printf("%d\n", qpow(a, b, p));
    return 0;
}

六十四位整数乘法 (ACW90)

abp 取模的值。

  • 算法一:

我们可以用类似快速幂的思想,使用二进制优化算法。将 b 拆分成二进制来看。假设 b 拆分成二进制之后是 2i,那么答案就是 a2i=a2i。由于 maxilog2b ,因此可以保证复杂度为 O(logb)

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

using LL = long long;

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

int main() {
    LL a, b, p;
    scanf("%lld%lld%lld", &a, &b, &p);
    printf("%lld\n", mul(a, b, p));
}
  • 算法二:

a×b(modp)=a×ba×b/p

因此可以使用 long double 存储 a×b/plong long 存储 a×b。二者相减即可。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

using LL = long long;

LL mul(LL a, LL b, LL p) {
    a %= p, b %= p;
    LL c = (long double)a * b / p;
    LL ans = a * b - c * p;
    ans += (ans < 0) ? p : 0;
    ans -= (ans >= p) ? p : 0;
    return ans;
}

int main() {
    LL a, b, p;
    scanf("%lld%lld%lld", &a, &b, &p);
    printf("%lld\n", mul(a, b, p));
}

最短 Hamilton 路径 (ACW91)

给定一张 n 个点的带权无向图,点从 0n1 标号,求起点 0 到终点 n1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0n1 不重不漏地经过每个点恰好一次。 n20

考虑状态压缩 dp。由于 n 的规模很小,可以直接用二进制枚举每个点的状态,其中 1 表示经过一次,0 表示没有经过。

设置状态 fi,j ,表示当前在第 i 个点,当前状态是 j 的最短路径。首先要枚举所有可行状态,刷表时枚举当前点和要走到的点,如果当前点没有被走到过或者要去的点已经走过一遍了,那么直接剪掉。最终答案即为 fn1,2n1

由此来看,复杂度为 O(2nn2),但是剪枝强度很大,可以跑过。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

const int N = 21;
int f[N][1 << N];
int n, w[N][N];

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            scanf("%d", &w[i][j]);
    memset(f, 0x3f, sizeof f);
    f[0][1 << 0] = 0;
    for (int i = 1; i < 1 << n; i ++ )
        for (int j = 0; j < n; j ++ ) if (i & (1 << j)) {
            for (int k = 0; k < n; k ++ ) {
                if (i & (1 << k)) continue;
                f[k][i | (1 << k)] = min(f[k][i | (1 << k)], f[j][i] + w[j][k]);
            }
        }
    printf("%d\n", f[n - 1][(1 << n) - 1]);
    return 0;
}

起床困难综合征 (ACW998)

本题思路是按位贪心。可以参照 01trie 进行理解。

形象地看,假设我有一个 n 位的二进制数,每一位都是 0,现在我可以把某一位变成一使得这个数最大,那么我肯定要变 n1 位,因为我只要把最高位设成 1,就会比 0n2 位都设成 1 要大 1

本题也可以用这种思路,将答案从高位到低位按最优方案填充即可。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

const int N = 100010;

int n, m, w[N];
string str[N];

int calc(int bit, int now) {
    for (int i = 1; i <= n; i ++ ) {
        int x = w[i] >> bit & 1;
        if (str[i] == "AND") now &= x;
        else if (str[i] == "OR") now |= x;
        else now ^= x;
    }
    return now;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
        cin >> str[i] >> w[i];
    
    int val = 0, ans = 0;
    for (int i = 29; ~i; i -- ) {
        int res0 = calc(i, 0);
        int res1 = calc(i, 1);
        if (val + (1 << i) <= m && res0 < res1)
            val += 1 << i, ans += res1 << i;
        else ans += res0 << i;
    }
    cout << ans << endl;
    
    return 0;
}
posted @   Link-Cut-Y  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示