OI技巧集合

OI巧集合

二进制

置1:x | (1 << k)

置0:x & ~(1 << k)

取反:x ^ (1 << k)

将最后一个有效位置零:x & (x - 1)

将有效位后面的所有0置1:x | (x - 1)

判断是否是2的幂次: x & (x - 1) == 0

判断奇偶性: x & 1

补码:
一个数的补码是将lowbit保留下来,比lowbit高的位进行取反。
因此我们有恒等式:~x = -x - 1,同时也是lowbit函数的原理。

逻辑位组:
将二进制数每一位看做是一个bool变量,通过位移等运算进行置位等。
例如,在生成不重复排列中,我们可以通过检查该位上是否出现过该字母,通过逻辑位组实现。性能优于bool数组。

class Solution
{
public:
    vector<string> ans;
    void permutation(string &s, int idx)
    {
        if (idx == s.size())
        {
            ans.push_back(s);
            return;
        }
        int vis = 0;
        for (int i = idx; i < s.size(); i++)
        {
            if (((vis >> (s[i] - 'a')) & 1) == 0)
            {
                swap(s[idx], s[i]);
                permutation(s, idx + 1);
                swap(s[idx], s[i]);
                vis |= (1 << (s[i] - 'a'));
            }
        }
    }
    vector<string> permutation(string s)
    {
        permutation(s, 0);
        return ans;
    }
};

GCC内置函数 _builtin

  • int __builtin_ffs (unsigned int x)
    返回x的最后一位1的是从后向前第几位,比如7368(1110011001000)返回4。
  • int __builtin_clz (unsigned int x)
    返回前导的0的个数。
  • int __builtin_ctz (unsigned int x)
    返回后面的0个个数,和__builtin_clz相对。
  • int __builtin_popcount (unsigned int x)
    返回二进制表示中1的个数。
  • int __builtin_parity (unsigned int x)
    返回x的奇偶校验位,也就是x的1的个数模2的结果。

此外,这些函数都有相应的usigned long和usigned long long版本,只需要在函数名后面加上l或ll就可以了,比如int __builtin_clzll。

注意,只在GCC环境下可用。

lowbit:
lowbit返回最低位的 1 1 1

int lowbit(int x)
{
    return x & -x;
}

二分法计数1的个数:

int popcount(x)
{
    x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
    x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
    x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
    return x;
}

汉明距离:
Hamming’s distance是两个二进制串中不同位的个数,即__builtin_popcount(a ^ b)

枚举与子集类:
二进制子集表示法

每一个二进制位都代表一个元素,如果该位为1,表示元素存在,否则元素不存在。
因此,我们得到以下运算:

集合运算二进制运算
a ∩ b a \cap b aba & b
a ∪ b a \cup b aba | b
a ˉ \bar{a} aˉ~a
a − b a - b aba & (~b)

枚举二进制子集:

将最后一个1置0,然后将后面的子集元素全部置1,从头开始,可以保证枚举的是所有的子集。

for (int i = stats; i; i = (i - 1) & stats)
{
	// Code
}

枚举大小固定的子集:

Gosper’s Hack是一种生成 n n n元集合所有 k k k元子集的算法,它巧妙地利用了位运算。这里我们先给出代码:

void GospersHack(int k, int n)
{
    int cur = (1 << k) - 1;
    int limit = (1 << n);
    while (cur < limit)
    {
        // do something
        int lb = cur & -cur;
        int r = cur + lb;
        // cur = ((r ^ cur) >> __builtin_ctz(lb) + 2) | r;
        cur = (((r ^ cur) >> 2) / lb) | r;
    }
}

异或前缀和:

定义异或前缀和函数 sumXor ( x ) \text{sumXor}(x) sumXor(x),表示 0 ⊕ 1 ⊕ 2 ⊕ ⋯ ⊕ 0 \oplus 1 \oplus 2 \oplus \dots \oplus 012

连续四个相邻整数的异或和有:

∀ i ∈ Z , 4 i ⊕ ( 4 i + 1 ) ⊕ ( 4 i + 2 ) ⊕ ( 4 i + 3 ) = 0 \forall i \in Z,4i \oplus (4i+1) \oplus (4i+2) \oplus (4i+3) = 0 iZ4i(4i+1)(4i+2)(4i+3)=0

因此,我们有:

sumXor ( x ) = { x , x = 4 k , k ∈ Z ( x − 1 ) ⊕ x , x = 4 k + 1 , k ∈ Z ( x − 2 ) ⊕ ( x − 1 ) ⊕ x , x = 4 k + 2 , k ∈ Z ( x − 3 ) ⊕ ( x − 2 ) ⊕ ( x − 1 ) ⊕ x , x = 4 k + 3 , k ∈ Z \text{sumXor}(x)= \begin{cases} x,& x=4k,k\in Z\\ (x-1) \oplus x,& x=4k+1,k\in Z\\ (x-2) \oplus (x-1) \oplus x,& x=4k+2,k\in Z\\ (x-3) \oplus (x-2) \oplus (x-1) \oplus x,& x=4k+3,k\in Z\\ \end{cases} sumXor(x)=x,(x1)x,(x2)(x1)x,(x3)(x2)(x1)x,x=4k,kZx=4k+1,kZx=4k+2,kZx=4k+3,kZ

具体的化简:

sumXor ( x ) = { x , x = 4 k , k ∈ Z 1 , x = 4 k + 1 , k ∈ Z x + 1 , x = 4 k + 2 , k ∈ Z 0 , x = 4 k + 3 , k ∈ Z \text{sumXor}(x)= \begin{cases} x,& x=4k,k\in Z\\ 1,& x=4k+1,k\in Z\\ x+1,& x=4k+2,k\in Z\\ 0,& x=4k+3,k\in Z\\ \end{cases} sumXor(x)=x,1,x+1,0,x=4k,kZx=4k+1,kZx=4k+2,kZx=4k+3,kZ

异或前缀和:

类比于前缀和,异或前缀和能实现区间查询,不修改数组。

LeetCode 1310

class Solution
{
public:
    vector<int> xorQueries(vector<int> &arr, vector<vector<int>> &queries)
    {
        vector<int> psum;
        psum.push_back(0);
        for (int i : arr)
        {
            psum.push_back(psum.back() ^ i);
        }

        vector<int> ans;

        for (vector<int> &query : queries)
        {
            ans.push_back(psum[query[1] + 1] ^ psum[query[0]]);
        }

        return ans;
    }
};

常见函数

取顶或取底:

ll fdiv(ll a, ll b) { return a / b - ((a ^ b) < 0 && a % b); }
ll cdiv(ll a, ll b) { return a / b + ((a ^ b) > 0 && a % b); }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值