n 扇门都是逐位运算,位与位互不影响:用 0 和全 1 各穿越一次求出每位输入 0/1 的结果,再从高位到低位贪心,免费位不花预算、其余位在不超过 m 时买下。
OJ: roj
题目 ID: 3002
难度:提高
标签:位运算贪心python
创建: 2026-10-01 09:19
更新: 2026-10-07 13:50
形式化题目
给定
求
样例 AND 5、OR 6、XOR 7:
| 0 | 0 | 6 | 1 |
| 1 | 1 | 7 | 0 |
| 2 | 0 | 6 | 1 |
| 3 | 1 | 7 | 0 |
| 4 | 4 | 6 | 1 |
| 10 | 0 | 6 | 1 |
为什么偶数得 1、奇数得 0?只看最低位:AND 5、OR 6 都把 XOR 7 把它翻一次:偶数 AND 5 把第 2 位以上全部清零,OR 6 再把第 1、2 位固定成 XOR 7 后全部归零。
所以最终伤害只能取 0 或 1,最大值是 AND 3、
正解
思路
朴素做法:枚举
瓶颈一:候选太多。 要枚举
关键观察:位与位互不影响。 题面规定两个数前补 0 到等长再逐位运算,
AND / OR / XOR 又都是逐位运算——所以一次运算的第
于是
先建立一点直觉。一扇门对某一比特只有四种可能的作用,写成
| 单扇门 | 该位输入 0 的输出 | 该位输入 1 的输出 | 作用 |
|---|---|---|---|
AND 0 |
0 | 0 | 强制清零 |
AND 1 |
0 | 1 | 原样透传 |
OR 0 |
0 | 1 | 原样透传 |
OR 1 |
1 | 1 | 强制置一 |
XOR 0 |
0 | 1 | 原样透传 |
XOR 1 |
1 | 0 | 该位取反 |
六种门按参数的一位展开就是这四种:AND 是恒 0、OR 与 XOR 是透传;
该位是 1 时 AND 是透传、OR 是恒 1、XOR 是取反。多扇门串起来只是这四种作用的复合,
复合结果仍落在这四种里。所以 00 是恒 0,11 是恒 1,01 是透传,10 是取反。
瓶颈二:求
瓶颈三:
这是加权最大化,高位权重大于所有低位之和(a 记录已经花掉的预算即可。对第
- 若
:输入 0 就已经得到 1,白拿,令 ,不花一分预算; - 否则若
且 :花 买下这一位, ; - 否则这一位拿不到 1(
,或预算不够),令 。
第 1 条必须排在第 2 条之前,这是全题最容易写错的一步:既然输入 0 就能拿到 1,
再花 AND 6、OR 1、OR 27、XOR 0 时
(此时
三个瓶颈各消除一层,算法就定下来了:读入
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 09:20
# update_at: 2026-10-01 09:26
import sys
from collections.abc import Callable
from operator import and_, or_, xor
type BitOp = Callable[[int, int], int] # 一扇门的运算:把攻击力与参数 t 按位合成
type Door = tuple[BitOp, int] # 一扇门 = 运算 + 参数
OPS: dict[bytes, BitOp] = {b'AND': and_, b'OR': or_, b'XOR': xor}
def fold(doors: list[Door], x: int) -> int:
"""把攻击力 x 依次送过所有防御门,返回最终伤害。"""
for op, t in doors:
x = op(x, t)
return x
def best_attack(lo: int, hi: int, m: int) -> int:
"""在 0..m 中选初始攻击力,返回最大伤害。
lo = f(0)、hi = f(2^B-1),它们的第 b 位就是第 b 位输入 0 / 输入 1 的输出。
从高位往低位贪心:输入 0 就有 1 的位白拿,否则预算够且买得到 1 才买。
"""
ans = a = 0 # ans = 最终伤害;a = 已花掉的预算(构造中的初始攻击力,低位暂为 0)
for bit in range(max(lo, hi, m).bit_length() - 1, -1, -1):
free = lo >> bit & 1 # 输入 0 就已经是 1:白拿,不花预算
affordable = a + (1 << bit) <= m # 这一位还买得起吗(高位已定,低位尚为 0)
buy = not free and affordable and hi >> bit & 1 # 花钱能买到 1 才花,否则放弃
ans |= (free or buy) << bit # free / buy 都是真值,位移后就是这一位的值
a += buy << bit # 只有真花钱时才抬高初始攻击力
return ans
def solve() -> None:
data = iter(sys.stdin.buffer.read().split())
n, m = int(next(data)), int(next(data))
doors: list[Door] = [(OPS[next(data)], int(next(data))) for _ in range(n)]
# 每扇门都是逐位运算,某一位的输出只取决于输入的同一位。于是把 x 的所有位一起摆 1,
# 一次穿越就同时拿到「每位输入 1 的结果」hi;输入 0 的 lo 同理,两次穿越即可。
top = max(m, max((t for _op, t in doors), default=0)).bit_length() # 需要决定的位数
lo, hi = fold(doors, 0), fold(doors, (1 << top) - 1)
print(best_attack(lo, hi, m))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 13:35
* update_at: 2026-10-06 13:35
*/
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN = 100005; // n 最大约 1e5
const int OP_AND = 0; // 门运算的编号
const int OP_OR = 1;
const int OP_XOR = 2;
int n; // 防御门数量
ll m; // 初始攻击力的上限
int door_op[MAXN]; // door_op[i]:第 i 扇门的运算编号
ll door_t[MAXN]; // door_t[i]:第 i 扇门的参数 t
// 把攻击力 x 依次送过所有 n 扇门,返回最终伤害。
ll apply_doors(ll x) {
for (int i = 0; i < n; i++) {
if (door_op[i] == OP_AND) {
x = x & door_t[i];
} else if (door_op[i] == OP_OR) {
x = x | door_t[i];
} else {
x = x ^ door_t[i];
}
}
return x;
}
void read_input() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
char op[8];
cin >> op >> door_t[i];
if (strcmp(op, "AND") == 0) {
door_op[i] = OP_AND;
} else if (strcmp(op, "OR") == 0) {
door_op[i] = OP_OR;
} else {
door_op[i] = OP_XOR;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
read_input();
// 每扇门都是逐位运算,某一位的输出只取决于输入的同一位。把所有需要决定的位一起摆 1,
// 一次穿越就同时得到「每位输入 1 的结果」hi;输入 0 的 lo 同理,两次穿越即可。
ll max_val = m;
for (int i = 0; i < n; i++) {
if (door_t[i] > max_val) {
max_val = door_t[i];
}
}
int top = 0; // 需要决定的位数
while ((1LL << top) <= max_val) {
top++;
}
ll lo = apply_doors(0); // 第 b 位 = 该位输入 0 时的输出
ll hi = apply_doors((1LL << top) - 1); // 第 b 位 = 该位输入 1 时的输出
// 从高位到低位贪心:输入 0 就是 1 的位白拿;否则预算够且花钱能买到 1 才买。
ll ans = 0; // 最终最大伤害
ll used = 0; // 已花掉的预算(即构造中的初始攻击力)
for (int b = top - 1; b >= 0; b--) {
ll w = 1LL << b;
int lo_bit = (lo >> b) & 1;
int hi_bit = (hi >> b) & 1;
if (lo_bit == 1) {
ans |= w;
} else if (hi_bit == 1 && used + w <= m) {
ans |= w;
used += w;
}
}
cout << ans << "\n";
return 0;
}复杂度
| 阶段 | 代价 |
|---|---|
| 读入 |
|
| 两次穿越求 |
|
| 逐位贪心 | |
| 合计 |
空间
总结
本题的核心只有两句话:
- 按位独立:AND / OR / XOR 逐位运算,所以
的每一位只由 的同一位决定, 个候选取值根本不必枚举;把所有位一起摆 1(再摆 0)穿越两次,就能一次性测出全部位的性质。 - 高位优先贪心:位权
大于所有低位权重之和,所以从高位往低位能拿就拿, 但要先用累计预算 a检查;输入 0 就能拿到的位免费,绝不为它花钱。
这是一个「
图示解析
下面这张表把样例 AND 5、OR 6、XOR 7)的贪心决策逐步摊开,
展示
| 位 |
预算 |
判定 | 伤害位 | ||||
|---|---|---|---|---|---|---|---|
| 3 | 8 | 0 | 0 | 0 | 0 | 0 | |
| 2 | 4 | 0 | 0 | 0 | 同上 | 0 | 0 |
| 1 | 2 | 0 | 0 | 0 | 同上 | 0 | 0 |
| 0 | 1 | 1 | 0 | 0 | 输入 0 就已经是 1,白拿 | 0 | 1 |
贪心构造出的 free 必须优先于 buy。
