本文涉及知识点
位运算、状态压缩、枚举子集汇总
动态规划汇总
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++**实现。