【状态机dp 状态压缩】1349. 参加考试的最大学生数

本文涉及知识点

C++动态规划
位运算、状态压缩、枚举子集汇总

LeetCode 1349. 参加考试的最大学生数

给你一个 m * n 的矩阵 seats 表示教室中的座位分布。如果座位是坏的(不可用),就用 ‘#’ 表示;否则,用 ‘.’ 表示。
学生可以看到左侧、右侧、左上、右上这四个方向上紧邻他的学生的答卷,但是看不到直接坐在他前面或者后面的学生的答卷。请你计算并返回该考场可以容纳的同时参加考试且无法作弊的 最大 学生人数。
学生必须坐在状况良好的座位上。

示例 1:
在这里插入图片描述

输入:seats = [[“#”,“.”,“#”,“#”,“.”,“#”],
[“.”,“#”,“#”,“#”,“#”,“.”],
[“#”,“.”,“#”,“#”,“.”,“#”]]
输出:4
解释:教师可以让 4 个学生坐在可用的座位上,这样他们就无法在考试中作弊。
示例 2:

输入:seats = [[“.”,“#”],
[“#”,“#”],
[“#”,“.”],
[“#”,“#”],
[“.”,“#”]]
输出:3
解释:让所有学生坐在可用的座位上。
示例 3:

输入:seats = [[“#”,“.”,“.”,“.”,“#”],
[“.”,“#”,“.”,“#”,“.”],
[“.”,“.”,“#”,“.”,“.”],
[“.”,“#”,“.”,“#”,“.”],
[“#”,“.”,“.”,“.”,“#”]]
输出:10
解释:让学生坐在第 1、3 和 5 列的可用座位上。

提示:

seats 只包含字符 ‘.’ 和’#’
m == seats.length
n == seats[i].length
1 <= m <= 8
1 <= n <= 8

动态规划

动态规划的状态表示

dp’[i][mask] 表示已经处理了i行,最后一行考生的状态为mask。 (1<<x)&mask 表示第x列有考生。
pre = dp’[i] dp = dp’[i+1]
使用滚动向量空间复杂度:O(2n)

动态规划的转移方程

枚举当前mask,mask1表示坏椅子的状态。如果mask&mask1 说明怀椅子有人坐了,非法。
预处理所有本行没有挨着一起的状态:mask&(mask<<1)为0
mask2 = (mask << 1 ) | ( mask >>1) 前一行preMask & mask2必须为0,即这几位必须为0,其它位任意。
mask3 = (~mask2)&(1 << n ) 枚举mask3所有的子集sub(包括0),取pre[sub]的最大值。
一行的转移方程复杂度为:O(3n)
总时间复杂度为:O(m3n)

动态规划的初始值

pre全部为0。

动态规划的填表顺序

i = 0 to m-1

动态规划的返回值

pre的最大值

代码

核心代码

class CBitCounts
{
public:
	CBitCounts(int iMaskCount)
	{
		for (int i = 0; i < iMaskCount; i++)
		{
			m_vCnt.emplace_back(bitcount(i));
		}
	}
	template<class T>
	static int bitcount(T x) {
		int countx = 0;
		while (x) {
			countx++;
			x &= (x - 1);
		}
		return countx;
	}
	vector<int> m_vCnt;
};

class Solution {
public:
	int maxStudents(vector<vector<char>>& seats) {
		vector<int> vilid;
		const int R = seats.size();
		const int C = seats.front().size();
		const int iMaskCnt = 1 << C;
		for (int i = 0; i < iMaskCnt; i++) {
			if (i & (i << 1)) { continue; }
			vilid.emplace_back(i);
		}
		CBitCounts bc(iMaskCnt);
		vector<int> pre(iMaskCnt);
		for (int r = 0; r < R; r++) {
			vector<int> dp(iMaskCnt);
			int iSeatMask = 0;
			for (int c = 0; c < C; c++) {
				if ('.' == seats[r][c]) { continue; }
				iSeatMask |= (1 << c);
			}
			for (const auto& mask : vilid) {				
				if (mask & iSeatMask) { continue; }
				auto mask2 = (mask << 1) | (mask >> 1);
				auto mask3 = (~mask2) & (iMaskCnt - 1);
				for (int sub = mask3; ; sub = (sub - 1) & mask3) {
					dp[mask] = max(dp[mask], pre[sub] + bc.m_vCnt[mask]);
					if (0 == sub) { break; }
				}
			}
			pre.swap(dp);
		}
		return *std::max_element(pre.begin(), pre.end());
	}
};

单元测试

template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
	Assert::AreEqual(t1, t2);
}
void AssertEx( double t1,  double t2)
{
	auto str = std::to_wstring(t1) + std::wstring(1,32) + std::to_wstring(t2);
	Assert::IsTrue(abs(t1 - t2) < 1e-5,str.c_str() );
}

template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{
	Assert::AreEqual(v1.size(), v2.size());
	for (int i = 0; i < v1.size(); i++)
	{
		Assert::AreEqual(v1[i], v2[i]);
	}
}

template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{
	sort(vv1.begin(), vv1.end());
	sort(vv2.begin(), vv2.end());
	Assert::AreEqual(vv1.size(), vv2.size());
	for (int i = 0; i < vv1.size(); i++)
	{
		AssertEx(vv1[i], vv2[i]);
	}
}

namespace UnitTest
{
	vector<vector<char>> seats;
	TEST_CLASS(UnitTest)
	{
	public:
		TEST_METHOD(TestMethod00)
		{
			seats = { {'#','.','#','#','.','#'},
			  {'.','#','#','#','#','.'},
			  {'#','.','#','#','.','#'} };
			auto res = Solution().maxStudents(seats);
			AssertEx(4, res);
		}
		TEST_METHOD(TestMethod01)
		{
			seats = { {'.','#'},
			  {'#','#'},
			  {'#','.'},
			  {'#','#'},
			  {'.','#'} };
			auto res = Solution().maxStudents(seats);
			AssertEx(3, res);
		}
		TEST_METHOD(TestMethod02)
		{
			seats = { {'#','.','.','.','#'},
			  {'.','#','.','#','.'},
			  {'.','.','#','.','.'},
			  {'.','#','.','#','.'},
			  {'#','.','.','.','#'} };
			auto res = Solution().maxStudents(seats);
			AssertEx(10, res);
		}
	};
}

扩展阅读

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关推荐

我想对大家说的话
喜缺全书算法册》以原理、正确性证明、总结为主。
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值