位运算:利用计算机的二进制计算原理进行运算的 C++ 运算符

一、位运算的实质和定义

二进制是计算机的基本操作单位,而位运算就是计算机采用的基本运算方式。在计算机内部,所有数据都按照二进制的方式储存和进行处理,因此位运算在计算机科学中尤为重要。由于位运算是计算机进行处理和运算的基础,所以使用位运算有以下几点好处:

  • 执行效率相比传统的加减乘除四则运算较高。
  • 定义相比传统四则运算在计算机中更加明确且不容易出现歧义。
  • 一些高级的数据结构和算法(比如线段树和树状数组)需要使用位运算提高运行效率和代码简洁程度。

但是,我们还是需要处理一些特殊的问题,比如运算符的优先级问题和运算的基本原理。
综上所述,位运算是计算机的基本运算,具有执行效率高的优点,但也存在优先级容易混淆、容易概念不清的缺点。

二、常见的位运算

下面我将用一张表格来说明位运算的分类和运算方式。

运算符运算方式举例
与(&把两个数的每个二进制位进行逻辑与运算,只有两个位都为 1 1 1 ,结果才是 1 1 1 3   &   5 = ( 11 ) 2   &   ( 101 ) 2 = ( 1 ) 2 = 1 3 \text{ }\&\text{ } 5=(11)_2\text{ }\&\text{ }(101)_2=(1)_2=1 3 & 5=(11)2 & (101)2=(1)2=1
或(|把两个数的每个二进制位进行逻辑或运算,两个位中只要有一个是 1 1 1 ,结果就是 1 1 1 3   ∣   5 = ( 11 ) 2   ∣   ( 101 ) 2 = ( 111 ) 2 = 7 3 \text{ }|\text{ } 5=(11)_2\text{ }|\text{ }(101)_2=(111)_2=7 3  5=(11)2  (101)2=(111)2=7
异或( ^把两个数的每个二进制位进行异或运算, 两个位不同,结果就是 1 1 1 ,否则就是 0 0 0 ,即 1 xor 0 = 1, 1 xor 1 = 0, 0 xor 0 = 0, 0 xor 1 = 1 3  xor  5 = ( 11 ) 2  xor  ( 101 ) 2 = ( 110 ) 2 = 6 3 \text{ xor } 5 = (11)_2 \text{ xor } (101)_2 = (110)_2 = 6 3 xor 5=(11)2 xor (101)2=(110)2=6
左移(<<把一个数左移指定位数,空白部分填充 0 0 0 3 < < 5 = ( 11 ) 2 < < 5 = ( 1100000 ) 2 = 96 3 << 5 = (11)_2 << 5 = (1100000)_2 = 96 3<<5=(11)2<<5=(1100000)2=96
右移(>>把一个数右移制定位数,去掉右移后被挤掉的部分。 5 > > 3 = ( 101 ) 2 > > 3 = 0 5 >> 3 = (101)_2 >> 3 = 0 5>>3=(101)2>>3=0

从上面的图标,我们可以看出,常见的位运算是与、或、异或三种运算,其中与预算和或运算的运算规则与其逻辑运算符的运算规则相近,但异或运算符的运算规则与传统的逻辑运算符有所不同。以上三种运算由于是计算机直接处理并运算,所以复杂度相对四则运算较低。

除此之外,位运算的优先级也应该注意。下面我将利用一张图表来说明位运算的优先级。

  1. 位运算内部:<< > >> > & > ^ > |
  2. 总体的运算优先级:位运算符 > 逻辑运算符 > 算术运算符

如果我们不确定需要使用哪种运算符,应该加上小括号 () 来指定运算顺序。

我们将会用下面的实例程序来说明位运算在 C++ 中的具体运用:

#include <bits/stdc++.h>

using namespace std;

int main()
{
	int a = 3 & 5;
	cout << a << endl; //1

	a = 3 | 5;
	cout << a << endl; //7

	a = 3 ^ 5
	cout << a << endl; //6

	a = 3 << 5;
	cout << a << endl; //96

	a = 5 >> 3;
	cout << a << endl; //0

	return 0;
}

三、利用位运算进行的简单操作

  1. 常见的用位运算代替传统四则运算的表达式
  • N < < 1 = 2 N N << 1 = 2N N<<1=2N
  • N > > 1 = ⌊ N 2 ⌋ N >> 1 = \lfloor \large{\frac{N}{2}} \rfloor N>>1=2N
  • k < < N = 2 N k k << N = 2^Nk k<<N=2Nk
  • N > > k = ⌊ N 2 k ⌋ N >> k = \lfloor \large{\frac{N}{2^k}} \rfloor N>>k=2kN
  1. 查找一个正整数 N N N 的第 k k k 位是否为 1 1 1

  结论:计算 N > > k   &   1 N>>k\text{ }\&\text{ }1 N>>k & 1 N   &   ( 1 < < k ) N\text{ }\&\text{ }(1<<k) N & (1<<k) 的值即可。
  证明:
(1)  ∵ \because N > > k = N 2 k N>>k=\large{\frac{N}{2^k}} N>>k=2kN (右移 >> 的运算规则) ,
∴ \therefore 计算 N > > k   &   1 N >> k \text{ } \& \text{ } 1 N>>k & 1 的值就可以判断 N N N 右移 k k k 位后的最后一位是否是 1 1 1
∴ \therefore 我们可以得到 N N N 的第 k k k 位是否是 1 1 1
(1)的代码实例:

#include <bits/stdc++.h>

using namespace std;

int n, k;

int main()
{
	cin >> n >> k; //n的第k位
	
	cout << (n >> k & 1) << endl; 
	
	return 0;
}

(2)  ∵ \because 1 < < k = 2 k 1<<k=2^{k} 1<<k=2k (左移 << 的运算规则),
∴ \therefore 1 < < k 1<<k 1<<k 是一个除了第 k k k 位的值是 1 1 1 ,其他位上的值都是 0 0 0 的二进制数。
∴ \therefore 如果 N N N 的第 k k k 位是 1 1 1 N & ( 1 < < k ) N \& (1 << k) N&(1<<k) 的值就一定是 1 1 1 ,反之就一定是 0 0 0
(2)的代码实例:

#include <bits/stdc++.h>

using namespace std;

int n, k;

int main()
{
	cin >> n >> k;

	cout << (n & (1 << k)) << endl; //此处输出的值应该和上面的相同

	return 0;
}

  应用场景:
  在状态压缩中,需要应用这种方法来取出某一位的值来读取相应的信息。比如一行灯的亮灭情况,记亮为 1 1 1 ,不亮为 0 0 0 ,则一种可能的状态可以表示为: 10001111 10001111 10001111 。这时如果我们想在 O ( 1 ) O(1) O(1) 的时间复杂度内查取到第 k k k 盏灯的亮灭情况,则需要利用上面的办法,解决问题。
  状态压缩在 状态压缩 DP 中的应用较为广泛,在一些高级数据结构中也有用处。

  1. N N N 的第 k k k 位取反
    设变化后的 N N N N ′ N' N
    N > > k   &   1 = 0 N>>k\text{ }\&\text{ }1=0 N>>k & 1=0 时,
    ∵ \because N ′ = N + 2 k × ( 1 − 0 ) = N + 2 k N'=N+2^k \times (1-0)=N+2^k N=N+2k×(10)=N+2k
    ∴ \therefore N ← N + ( 1 < < k ) N \leftarrow N+(1<<k) NN+(1<<k) ,即 N += (1 << k)
    N > > k   &   1 = 1 N>>k\text{ }\&\text{ }1=1 N>>k & 1=1 时,
    ∵ \because N ′ = N + 2 k × ( 0 − 1 ) = N − 2 k N'=N+2^k \times (0-1)=N-2^k N=N+2k×(01)=N2k
    ∴ \therefore N ← N − ( 1 < < k ) N \leftarrow N-(1<<k) NN(1<<k) ,即 N -= (1 << k)
    代码实例:
#include <bits/stdc++.h>

using namespace std;

int n, k;

int main()
{
	cin >> n >> k;
	
	if ((n >> k & 1) == 0)
	{
		n += (1 << k);
	}
	else
	{
		n -= (1 << k);
	}
	
	cout << n << endl;
	// 或者去掉上面的条件判断和输出,改为:
	// cout << ((n >> k & 1) == 0 ? n + (1 << k) : n - (1 << k)) << endl;
	
	return 0;
}

四、利用位运算解决实际 OI 问题

  1. 题面描述
    1
  2. 解题方案
    先考虑第一行的 2 5 = 32 2^5=32 25=32 种状态,每一种状态中由五位 0 0 0 1 1 1 组成。如果第 1 1 1 行确定,下一行尝试让上一行所有数都变成 1 1 1 。到达最后一行,判断最后一行是否都是 1 1 1 并且转换次数小于等于 6 6 6 。最后,记录答案并输出最小值。
#include <bits/stdc++.h>

using namespace std;

#define ONLINE_JUDGE 1

const int N = 507;
const int dx[5] = {0, 1, 0, -1, 0}, dy[5] = {0, 0, 1, 0, -1};

int n, a[7][7], b[7][7], ans;

void solve(int x, int y)
{
    for (int i = 0; i < 5; ++i)
    {
        int xx = x + dx[i];
        int yy = y + dy[i];

        a[xx][yy] ^= 1;
    }
}

int main()
{
#if ONLINE_JUDGE
    freopen("light.in", "r", stdin);
    freopen("light.out", "w", stdout);
#endif

    cin >> n;
    
    while (n--)
    {
        for (int i = 1; i <= 5; ++i)
        {
            for (int j = 1; j <= 5; ++j)
            {
                scanf("%1d", &a[i][j]);
            }
        }

        ans = 10;
        memcpy(b, a, sizeof(a));

        for (int i = 0; i < 32; ++i)
        {
            memcpy(a, b, sizeof(b));

            int tot = 0;

            for (int j = 1; j <= 5; ++j)
            {
                if (i & (1 << (j - 1)))
                {
                    solve(1, j);
                    tot++;
                }
            }

            for (int j = 2; j <= 5; ++j)
            {
                for (int k = 1; k <= 5; ++k)
                {
                    if (a[j - 1][k] == 0)
                    {
                        solve(j, k);
                        tot++;
                    }
                }
            }

            if (tot > 6) continue;

            int f = 1;
            for (int j = 1; j <= 5; ++j)
            {
                if (a[5][j] == 0)
                {
                    f = 0;
                    break;
                }
            }

            if (f)
            {
                ans = min(ans, tot);
            }
        }

        ans = ans == 10 ? -1 : ans;

        cout << ans << endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值