SOS DP(子集 DP)
Part 1:前置知识
1、状压 DP
2、基本的位运算操作
Part 2:SOS DP
(以下的内容大部分翻译至CF上的原文 )
1、例题引入
给定一个含
2、解题思路
法一:暴力枚举
-
我们可以枚举每一个
,再枚举集合中的所有元素 ,判断 是属于集合 。这样做的时间复杂度为 -
代码
for(int mask=0; mask<(1<<N); mask++)
for(int i=0; i<(1<<N); i++)
if((mask&i)==i)
F[mask]+=A[i];
法二:枚举子集
-
对于任意
,如果它做的二进制位上有 个 ,那么它就有 个子集,我们只需遍历这些子集便可。时间复杂度为
。(可由二项式定理证明) -
代码
for(int mask=0; mask<(1<<N); mask++)
{
F[mask]=A[0];
for(int i=mask; i>0; i=(i-1)&mask)
F[mask]+=A[i];
}
法三:SOS DP
-
在我们之前的方法中存在明显的缺陷:当一个状态的二进制位上有
个 时,它将被访问 次,存在重复的计算。而产生这种现象的原因就是:我们没有在
被不同 利用时建立一定的联系。我们应添加另一个状态来避免上述的重复计算。 -
我们定义状态
。现在我们把这个集合划分为不相交的组。设
,表示只有第 位以及更低位与 不同的 的集合。举个例子:
101 101 101 101 101 。其中 即为 的第 位至第 位,集合中的元素的加粗部分应与 保持一致。 -
现在让我们尝试将
与 建立联系,下面分两种情况讨论:-
的第 位是不难发现,
与 的第 位均为 。因此 仅有第 至 位与 不同,故有 -
的第 位是那么
就由两部分组成: 的第 位为 ,即为 的第 位为 ,即为
-
-
综上,可以得出结论
不难计算,这种做法的时间复杂度为
-
代码(二维版)
for(int mask=0; mask<(1<<N); mask++)
{
dp[mask][-1] = A[mask];
for(int i=0; i<N; i++)
{
if(mask&(1<<i))
dp[mask][i]=dp[mask][i-1]+dp[mask^(1<<i)][i-1];
else
dp[mask][i]=dp[mask][i-1];
}
F[mask]=dp[mask][N-1];
}
- 代码(一维版)
for(int i=0; i<(1<<N); i++)
F[i]=A[i];
for(int i=0; i<N; i++)
{
for(int mask=0; mask<(1<<N); mask++)
{
if(mask&(1<<i))
F[mask]+=F[mask^(1<<i)];
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下