Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务活动详情
动态规划 + 状态压缩
一、题目描述
给定一个M*N的矩阵表示教室的座位,好的用'.'表示,坏的用'#'表示。规定每个学生在其九宫格的左、左上、右、右上四个位置不能放其他学生,求最多可以放多少个学生。
数据范围
N、M <= 8
二、思路分析
朴素想法: 直接暴搜,复杂度为指数级,不可行。考虑剪枝,由题目的摆放规则,冲突的情况很多,所以能减掉绝大部分情况,且N、M也不是特别大,有可能过时间限制。但这算是下策,由于时间限制没有去尝试,考虑其他方式。
进阶想法: 我们思考下学生之间的影响关系,可知只有前后排的人会互相影响,也就是说学生的摆放是有后效性的。
可以消除这种后效性吗?解决方法就是保存当前一行学生的摆放状态,后续就能根据之前保存的状态去转移,从而避免已经摆放了的前排对后排的影响。
摆放状态怎么保存呢?一行学生就只有8个,很明显,这只需要一个状态压缩就行了。用二进制下的每一位去表示每个位置是否摆放学生,一个不大于 2^8 的数字就能表示所有状态。
题解: 故我们设 f[i][j] 表示做到第 i 行,且第 i 行的学生摆放状态是 j 的最大摆放数量。转移方程就是【当前这行的个数】 + 【做到上一行时的最大数量】
即 f[i][k] = max( f[i-1][j] ) ,其中 k 和 j 两个状态不冲突(依题意)
三、AC代码
class Solution {
public:
int f[8][256];
// 求单独一个状态是否合法,合法的话有几个学生
int js1(int x, int r, vector<vector<char>>& seats){
int ans=0;
for (int i=1,d=0; i<=x; i*=2,d++) {
if (i&x) {
if ((i*2)&x) return 0;
if (seats[r][d] == '#') return 0;
}
}
while (x) {
ans+=(x%2);
x/=2;
}
return ans;
}
// 判断两个状态是否冲突
int js2(int x, int y) {
for (int i=1; i<=y; i*=2) {
if (!(i&y)) continue;
if (((i*2)&x) || ((i/2)&x)) return 0;
}
return 1;
}
int maxStudents(vector<vector<char>>& seats) {
int ans=0;
int n = seats.size();
int m = seats[0].size();
int fm = (1<<m),fn = (1<<n);
for (int i=0; i<fm; i++) {
f[0][i] = js1(i,0,seats);
ans = max(ans, f[0][i]);
}
for (int i=1; i<n; i++) {
for (int j=0; j<fm; j++) {
// 做的时候这里遇到了坑,没有把 0 的状态转移好,直接判成continue了
f[i][0] = max(f[i][0], f[i-1][j]);
for (int k=0; k<fm; k++) {
int now = js1(k,i,seats);
if (k==0 || (!js2(j,k))) {
continue;
}
f[i][k] = max(f[i][k], f[i-1][j] + now);
ans = max(ans, f[i][k]);
}
}
}
return ans;
}
};
四、总结
此类题目需要注重观察题目的要求,弄清楚转移条件再下手去写,这样才能避免写着写着把自己弄乱了。