将矩形贴合拓扑依赖抽象为位掩码先决条件,使用状态压缩记忆化搜索结合同色贪心涂满求最少拿起刷子次数。
OJ: roj
题目 ID: 1445
难度:普及
标签:状压 DP记忆化搜索拓扑排序
创建: 2026-09-30 10:46
更新: 2026-10-05 23:47
题目描述
平面上有
思路
pre_mask。记忆化搜索 dfs(mask, cur_color):若当前颜色还有可涂矩形,贪心全部涂完(不增加次数);否则枚举所有可涂矩形的颜色换刷,代价
参考代码
Python 版:
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-09-30 10:50
# update_at: 2026-09-30 10:50
import sys
from functools import cache
INF = 10**9
def build_pre_masks(rects: list[tuple[int, int, int, int, int]]) -> list[int]:
"""计算每个矩形必须在哪些矩形之后涂色:返回先决条件位掩码。
当矩形 u 的下边界与矩形 v 的上边界贴合且水平区间重叠时,u 是 v 的直接上方矩形。
"""
return [
sum(
1 << u
for u, (uy1, ux1, uy2, ux2, _) in enumerate(rects)
if uy2 == vy1 and not (ux2 <= vx1 or ux1 >= vx2)
)
for vy1, vx1, _, vx2, _ in rects
]
def min_brush_switches(n: int, rects: list[tuple[int, int, int, int, int]], pre_mask: list[int]) -> int:
"""记忆化搜索求涂满所有矩形的最少拿起刷子次数。"""
target = (1 << n) - 1
@cache
def dfs(mask: int, cur_color: int) -> int:
if mask == target:
return 0
# 当前可涂且与 cur_color 同色的矩形集合
same_color = [
i
for i in range(n)
if not (mask >> i & 1) and (mask & pre_mask[i]) == pre_mask[i] and rects[i][4] == cur_color
]
if same_color:
# 贪心:若手上有刷子且有同色可用,立刻全部涂掉不增加换刷次数
nxt_mask = mask
for i in same_color:
nxt_mask |= 1 << i
return dfs(nxt_mask, cur_color)
# 换新颜色刷子:尝试所有当前上方依赖已全部涂好的可用颜色
available = [
i
for i in range(n)
if not (mask >> i & 1) and (mask & pre_mask[i]) == pre_mask[i]
]
colors = {rects[i][4] for i in available}
return min((1 + dfs(mask, c) for c in colors), default=INF)
return dfs(0, 0)
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
tokens = list(data)
if not tokens:
return
it = iter(tokens)
n = next(it)
# y1, x1, y2, x2, color
rects = [
(next(it), next(it), next(it), next(it), next(it))
for _ in range(n)
]
pre_mask = build_pre_masks(rects)
ans = min_brush_switches(n, rects, pre_mask)
print(ans)
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* 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-05 23:47
* update_at: 2026-10-05 23:47
*/
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 16;
const int INF = 1e9;
int n;
// rects[i] = {y1, x1, y2, x2, color}
int rects[MAXN][5];
// pre_mask[i] 的第 u 位为 1 表示矩形 u 是 i 的紧靠上方矩形
int pre_mask[MAXN];
// memo[mask][color] 记忆化,color 范围 0..20,0 表示手中无刷子
int memo[1 << MAXN][21];
bool vis[1 << MAXN][21];
// 返回涂完剩余矩形所需的最少额外拿起刷子次数
int dfs(int mask, int cur_color) {
if (mask == (1 << n) - 1) return 0;
if (vis[mask][cur_color]) return memo[mask][cur_color];
vis[mask][cur_color] = true;
// 贪心:若手中有同色刷子,把所有可涂的同色矩形一次性涂完
int nxt_mask = mask;
for (int i = 0; i < n; ++i) {
if ((mask >> i & 1)) continue; // 已涂
if ((mask & pre_mask[i]) != pre_mask[i]) continue; // 上方未涂完
if (rects[i][4] != cur_color) continue; // 颜色不同
nxt_mask |= 1 << i;
}
if (nxt_mask != mask) {
memo[mask][cur_color] = dfs(nxt_mask, cur_color);
return memo[mask][cur_color];
}
// 枚举当前所有可涂矩形的颜色,换刷子
int ans = INF;
bool has_color[21] = {false};
for (int i = 0; i < n; ++i) {
if ((mask >> i & 1)) continue;
if ((mask & pre_mask[i]) != pre_mask[i]) continue;
int c = rects[i][4];
if (!has_color[c]) {
has_color[c] = true;
ans = min(ans, 1 + dfs(mask, c));
}
}
memo[mask][cur_color] = ans;
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> rects[i][0] >> rects[i][1] >> rects[i][2] >> rects[i][3] >> rects[i][4];
}
// 预处理每个矩形的紧靠上方矩形掩码
for (int v = 0; v < n; ++v) {
int mask = 0;
for (int u = 0; u < n; ++u) {
if (rects[u][2] == rects[v][0]) { // u 的下边界贴合 v 的上边界
if (!(rects[u][3] <= rects[v][1] || rects[u][1] >= rects[v][3])) {
mask |= 1 << u;
}
}
}
pre_mask[v] = mask;
}
cout << dfs(0, 0) << "\n";
return 0;
}