题目link:https://www.luogu.com.cn/problem/P2114
首先考虑暴力:直接枚举 $0$ ~ $m$ ,即初始攻击力,然后计算出其通过每扇门后的值, $ans$ 记录最大值即可。时间复杂度 $O(n$ $*$ $m)$ 。
考虑优化:首先可以知道 & (与) 、| (或)、^ (异或) 三个操作对于二进制来说是按位独立的,即它们对二进制的每一位是可以分别进行操作的。所以可以直接枚举二进制上的每一位数( $1$ 或 $0$ ),然后将这个数放入门里进行计算,最后的答案就是这些数字连在一起组成的二进制。
考虑实现:首先对于一个二进制数来说,要想它的值最大,那么和十进制数一样的,也是最高位越大越大,满足贪心。因此在枚举二进制上的每一位数时,需要判断是 $1$ 放入门里得出的是 $1$ ,还是 $0$ 放入门里是 $1$。根据贪心,肯定选择从门中出来为 $1$ 的更好。但是还需要考虑的一件事是当前枚举的二进制位数不能大于 $m$,所以有时只能选择 $0$ ,但是当其中有一位 $m$ 为 $1$,而选择为 $0$ ,那么之后不管是选 $1$ 还是 $0$ ,这个二进制的值永远不会比 $m$ 大,就可以任取了。但是注意一点,这个枚举二进制的位数是不一定等于 $m$ 的,因为大于 $m$ 的那些位都可以视为 $0$ ,但万一这些 $0$ 进门之后变成了 $1$ 那结果不就更优了吗?
考虑选 $1$ 更优,还是 $0$ 更优(假设此时能选 $1$,否则只能选 $0$):
- 选 $1$ 为 $1$ ,选 $0$ 为 $1$ :此时选两种情况都符合贪心,但是,选 $0$ 的话后面不管 $1$ 还是 $0$ 都可以选,因此此时选 $0$。
- 选 $1$ 为 $0$ ,选 $0$ 为 $0$ :此时选两种情况都一样,但是和上一种情况一样的,选 $0$ 。
- 选 $1$ 为 $1$ ,选 $0$ 为 $0$ :此时根据贪心,选 $1$ 。
- 选 $1$ 为 $0$ ,先 $0$ 为 $1$ :此时根据贪心,选 $0$ 。
实现方法:预处理出 $m$ 的二进制,枚举 $30$ ~ $0$ (注意是从高位到低位),将 $1$ 和 $0$ 放入门里,看哪个更优(判断方法见上),最后再将每一位的二进制数拼接起来即为 $ans$ 。时间复杂度 $O(n$ $*$ $30)$ 。
考虑进一步优化:对于每次将选择的 $1$ 或 $0$ 放入门里,可以考虑预处理,用一个二进制 $x$ 来表示 $31$ 位全是 $1$ 的一个数,和一个二进制 $y$ 来表示 $31$ 位全是 $0$ 的一个数,带入门里进行预处理,之后枚举二进制的时候就不用放进门里了。时间复杂度 $O(n)$ 。
最终优化代码:
1 #include <bits/stdc++.h> 2 #define INF 0x3f3f3f3f 3 using namespace std; 4 struct Str {char s[10]; int x;}stu[100010]; 5 int n, m, a[35], cnt, jud, ans, x, y; 6 void change() 7 { 8 for(; m; m >>= 1) a[cnt++] = m % 2; 9 return; 10 } 11 int main() 12 { 13 scanf("%d %d", &n, &m); 14 for(int i = 1; i <= n; ++i) 15 scanf("%s %d", stu[i].s + 1, &stu[i].x); 16 change(); x = (1 << 31) - 1, y = 0; 17 for(int i = 1; i <= n; ++i) 18 { 19 if(stu[i].s[1] == 'A') x &= stu[i].x, y &= stu[i].x; 20 if(stu[i].s[1] == 'O') x |= stu[i].x, y |= stu[i].x; 21 if(stu[i].s[1] == 'X') x ^= stu[i].x, y ^= stu[i].x; 22 } 23 for(int i = 30; i >= 0; --i) 24 { 25 if(a[i] == 0 && !jud) ans |= (y & (1 << i)); 26 else if((x & (1 << i)) && (y & (1 << i)) == 0) ans |= (1 << i); 27 else ans |= (y & (1 << i)), jud = 1; 28 } 29 printf("%d", ans); 30 return 0; 31 }