【状态机动态规划 状态压缩】1434. 每个人戴不同帽子的方案数

本文涉及知识点

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

LeetCode 1434. 每个人戴不同帽子的方案数

总共有 n 个人和 40 种不同的帽子,帽子编号从 1 到 40 。
给你一个整数列表的列表 hats ,其中 hats[i] 是第 i 个人所有喜欢帽子的列表。
请你给每个人安排一顶他喜欢的帽子,确保每个人戴的帽子跟别人都不一样,并返回方案数。
由于答案可能很大,请返回它对 10^9 + 7 取余后的结果。
示例 1:
输入:hats = [[3,4],[4,5],[5]]
输出:1
解释:给定条件下只有一种方法选择帽子。
第一个人选择帽子 3,第二个人选择帽子 4,最后一个人选择帽子 5。
示例 2:
输入:hats = [[3,5,1],[3,5]]
输出:4
解释:总共有 4 种安排帽子的方法:
(3,5),(5,3),(1,3) 和 (1,5)
示例 3:
输入:hats = [[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
输出:24
解释:每个人都可以从编号为 1 到 4 的帽子中选。
(1,2,3,4) 4 个帽子的排列方案数为 24 。
示例 4:
输入:hats = [[1,2,3],[2,3,5,6],[1,3,7,9],[1,8,9],[2,5,7]]
输出:111
提示:
n == hats.length
1 <= n <= 10
1 <= hats[i].length <= 40
1 <= hats[i][j] <= 40
hats[i] 包含一个数字互不相同的整数列表。

动态规划

预处理

hatToPeo 记录 帽子被那些人喜欢。帽子和人都从0开始。

动态规划状态表示

dp’[i][j]表示处理前i个帽子,j表示那些人已经分配了帽子。(1<<x )& j 表示第x个人是否分配了帽子。
pre[j] = dp’[i][j] dp[j] = dp’[i][j]
如果不用滚动向量,时间复杂度:O(m2n)
如果记录帽子被分配的状态,空间复杂度至少是O(2m)严重超时。

动态规划的转移方程

对于每种状态,只有n+1种后置状态。任何人都不戴此帽子,枚举每个人戴帽子。单状态转移方程的时间复杂度是O(n)。
故总时间复杂度:O(m2nn)

动态规划的初始状态

pre[0][0] = 1 ,其它为0。

动态规划的填表顺序

i = 0 to m-1

动态规划的返回值

pre.back()

代码

核心代码

template<int MOD = 1000000007>
class C1097Int
{
public:
	C1097Int(long long llData = 0) :m_iData(llData% MOD)
	{

	}
	C1097Int  operator+(const C1097Int& o)const
	{
		return C1097Int(((long long)m_iData + o.m_iData) % MOD);
	}
	C1097Int& operator+=(const C1097Int& o)
	{
		m_iData = ((long long)m_iData + o.m_iData) % MOD;
		return *this;
	}
	C1097Int& operator-=(const C1097Int& o)
	{
		m_iData = (m_iData + MOD - o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator-(const C1097Int& o)
	{
		return C1097Int((m_iData + MOD - o.m_iData) % MOD);
	}
	C1097Int  operator*(const C1097Int& o)const
	{
		return((long long)m_iData * o.m_iData) % MOD;
	}
	C1097Int& operator*=(const C1097Int& o)
	{
		m_iData = ((long long)m_iData * o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator/(const C1097Int& o)const
	{
		return *this * o.PowNegative1();
	}
	C1097Int& operator/=(const C1097Int& o)
	{
		*this /= o.PowNegative1();
		return *this;
	}
	bool operator==(const C1097Int& o)const
	{
		return m_iData == o.m_iData;
	}
	bool operator<(const C1097Int& o)const
	{
		return m_iData < o.m_iData;
	}
	C1097Int pow(long long n)const
	{
		C1097Int iRet = 1, iCur = *this;
		while (n)
		{
			if (n & 1)
			{
				iRet *= iCur;
			}
			iCur *= iCur;
			n >>= 1;
		}
		return iRet;
	}
	C1097Int PowNegative1()const
	{
		return pow(MOD - 2);
	}
	int ToInt()const
	{
		return m_iData;
	}
private:
	int m_iData = 0;;
};


class Solution {
public:
	int numberWays(vector<vector<int>>& hats) {
		const int N = hats.size();
		vector<vector<int>> hatToPeo(40);
		for (int peo = 0; peo < hats.size(); peo++) {
			for (const auto& hat : hats[peo]) {
				hatToPeo[hat - 1].emplace_back(peo);
			}
		}
		vector<C1097Int<>> pre(1 << N);
		pre[0] = 1;
		for (int i = 0; i < 40; i++) {
			auto dp = pre;//本帽子不选择	
			for (int j = 0; j < (1 << N); j++  ) {
				for (const auto& peo : hatToPeo[i]) {
					if (j & (1 << peo)) { continue; }
					dp[j | (1 << peo)] += pre[j];
				}
			}	
			pre.swap(dp);
		}
		return pre.back().ToInt();
	}
};

单元测试

template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
	Assert::AreEqual(t1, t2);
}

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<int>> hats;
	TEST_CLASS(UnitTest)
	{
	public:
		TEST_METHOD(TestMethod0)
		{
			hats = { {3,4},{4,5},{5} };
			auto res = Solution().numberWays(hats);
			AssertEx(1, res);
		}
		TEST_METHOD(TestMethod1)
		{
			hats = { {3,5,1},{3,5} };
			auto res = Solution().numberWays(hats);
			AssertEx(4, res);
		}
		TEST_METHOD(TestMethod2)
		{
			hats = { {1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4} };
			auto res = Solution().numberWays(hats);
			AssertEx(24, res);
		}
		TEST_METHOD(TestMethod3)
		{
			hats = { {1,2,3},{2,3,5,6},{1,3,7,9},{1,8,9},{2,5,7} };
			auto res = Solution().numberWays(hats);
			AssertEx(111, res);
		}
		TEST_METHOD(TestMethod4)
		{
			hats = { {1,3,5,10,12,13,14,15,16,18,19,20,21,27,34,35,38,39,40},{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40},{3,7,10,12,13,14,15,17,21,25,29,31,35,40},{2,3,7,8,9,11,12,14,15,16,17,18,19,20,22,24,25,28,29,32,33,34,35,36,38},{6,12,17,20,22,26,28,30,31,32,34,35},{1,4,6,7,12,13,14,15,21,22,27,28,30,31,32,35,37,38,40},{6,12,21,25,38},{1,3,4,5,6,7,8,9,10,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,34,35,36,37,38,39,40} };
			auto res = Solution().numberWays(hats);
			AssertEx(842465346, 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++**实现。

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

闻缺陷则喜何志丹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值