位运算笔记 / 贪心例五题解

位运算符号

左移 <<
右移 >>
或 |
与 &
取反 ~
异或 ^ (异或 0 不变,异或 1 取反)

位运算基础

去掉最后一位 x >> 1
在最后加一个 0 x << 1
在最后加一个 1 (x << 1) + 1 或者 (x << 1) | 1
把最后一位变成 1 x | 1
把最后一位变成 0 (x | 1) - 1
最后一位取反 x ^ 1
把右数第 k 位变成 1 x | (1 << (k - 1))
把右数第 k 位变成 0 x & (~ (1 << (k - 1)))
右数第 k 位取反 x ^ (1 << (k - 1))

位运算妙用(实例)

当题目出现需要枚举 数个有且仅有两个状态的东西时,可以利用二进制循环枚举

题目

本题大意:输入一个矩阵值和回合数,每个回合得到一行或者一竖的值的和,且被得到过的地方值变为 0,问得到的最大总值是多少?

分析

本题考查贪心,枚举
发现每次不同的选择,会对之后的贪心选择造成影响(因为被选值变成 0)这样子问题会十分复杂。
发现选择的顺序对总值没有影响,例如不妨假设最终的最优策略是选择两行一竖,而这三次的选择顺序最答案没有影响,所以是否可以先将所有行选择完,再加上剩余的竖,从而维护出最优解。

解题思路

先枚举所有行情况,再分别对每个行情况加入竖值,并从始至终维护出最优解

而枚举行情况就用到位运算的法子

从二进制视角,一共 n 行,每回合的取与不取,对应 0 和 1。所以从 0 到 ((1 << n) - 1),完美通过零一串反应所以行情况!

ac 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
//分别为行数,竖数,回合数
int n, m, k;
//存放矩形中每个值
int a[20][20];
//存放 取完行后剩下的竖数值
int list[20];
//计算行总值的函数
int cale(int st)
{
int sum = 0;
//循环st二进制的每一位,也就是每一行的取舍情况!
for (int i = 1; i <= n; i++)
{
//判断每一位是否为1,如果是1,就是取这行的值,并加入sum
if ((((st >> (i - 1)) & 1) == 1) && k != 0)
{
for (int j = 1; j <= m; j++)
sum += a[i][j];
k--;
}
//反之,将还留下的值存入list,方便后续继续求竖值
else
{
for (int j = 1; j <= m; j++)
list[j] += a[i][j];
}
}
return sum;
}
bool cmp(int a, int b)
{
return a > b;
}
int main()
{
cin >> n >> m >> k;
//维护k值,是个坑点,这里卡了两发
int temp = k;
int sum = 0;
//维护答案
int ans = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
sum += a[i][j];
}
//先特判
if (k >= n || k >= m) ans = sum;
else
{
//重点!!!st从0开始自增,注意要以二进制视角理解
for (int st = 0; st <= ((1 << n) - 1); st++)
{
k = temp;
memset(list, 0, sizeof(list));
sum = cale(st);
sort(list + 1, list + 1 + m, cmp);
for (int i = 1; i <= k; i++)
{
sum += list[i];
}
ans = max(ans, sum);
}
}
cout << ans << endl;
return 0;
}