携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
LCP 04. 覆盖
输入:
输出:
示例 1:
输入:n = 2, m = 3, broken = [[1, 0], [1, 1]]
输出:2
解释:我们最多可以放两块骨牌:[[0, 0], [0, 1]]以及[[0, 2], [1, 2]]。(见下图)
示例 2:
输入:n = 3, m = 3, broken = []
输出:4
解释:下图是其中一种可行的摆放方式
数据范围:
1 <= n <= 81 <= m <= 80 <= b <= n * m
思路
状压dp
这个题和蒙德里安的梦想有点像
只不过,不同的地方是这个题多了个禁用方块且是计算放置最大的数量
考虑一个这样子的关系,对于所有的状态 n行一共有 个状态
对于每一行来说,可以枚举推出下一行的所有状态 的复杂度
首先,不考虑不可用的格子。我们应该如何转移?
我们令 二进制中的0代表这一个格没有突出,1代表这一格突出了
比如 001000,此时只有001000这一个位置可以横着放一个,突出出去了
在放置时,要保证 前面那一行对应位置是有空位的,也就是 a & b 是 等于0的
然后方完横的以后,接下来就看一下当前行还可以放置几个竖立的
在统计没有突出的格子的时候,要考虑前一行突出来的 也就是 a | b这样
比如 01010101 这样的就一个也放不了 而 001001001 则可以放置3个多米诺骨牌
所以状态转移就出来了,即:
然后我们把broken这个限制再加上就可以了
代码
class Solution {
public:
int domino(int n, int m, vector<vector<int>>& broken) {
vector<int> b(m + 1);
vector<vector<int>> state(m + 1);
for (auto v : broken) b[1+v[1]] |= 1 << v[0];
for (int i = 1; i <= m; i ++)
for (int j = 0; j < 1 << n; j ++) {
if (j & b[i] || (i < m && (j & b[i+1]))) continue;
state[i].push_back(j);
}
auto count = [](int x) {
int res = 0;
while (x) res += x & 1, x >>= 1;
return res;
};
state[0].push_back(0);
vector<vector<int>> dp(m + 1, vector<int>(1 << n));
for (int i = 1; i <= m; i ++)
for (auto u : state[i])
for (auto v : state[i-1]) {
if (u & v) continue;
int tag = u | v | b[i];
int cnt = 0, cur = 0;
for (int i = 0; i < n; i ++) {
if (tag >> i & 1) cnt += cur / 2, cur = 0;
else cur ++;
}
cnt += cur / 2;
dp[i][u] = max(dp[i][u], dp[i-1][v] + cnt + count(v));
}
return dp[m][0];
}
};