子集和dp

子集和dp

用处

  1. 统计n维偏序,但是每一维的大小只能是2。
  2. 计算子集权值之和。

实际上以上两种问题是等价的。
例如目前有一个集合:101(其中1表示有某个物品,0表示没有)。
那该集合包涵的子集有4个:101,100,001,000。现在要把这4个集合的权值加起来。

按照第二种理解(用处),我们可以一位一位地来dp计算贡献。
\(f_{i,j}\) 表示正在计算数字j的贡献,处理到了第i位的答案(也可以理解为前i位与j可能不同,但后面的位都与j相同)。

转移方程:
若j的第i位为0,则这一位只能为0 :\(f_{i,j} = f_{i-1,j}\)
若j的第i位为1,则这一位能为1或0:\(f_{i,j} = f_{i-1,j} + f_{i-1,k}\)
其中,k的第i位是0,其余位与j相同,即 \(k = j \oplus 2^i\)

外层循环枚举i,可把i那一位滚掉。实际写的时候是在计算\(f_{i-1,k}\)时来更新\(f_{i,j}\)。反正 \(f_{i-1,j}\)也不会去更新其他数,所以内层枚举顺序不重要。

代码短小精悍:

for(int j = 1; j < (1<<K); ++j) f[j] = val[j];//init
for(int i = 0; i < K; ++i) 
  for(int j = 0; j < (1<<K); ++j)
    if(!(j >> i & 1)) f[j | (1 << i)] += f[j];

复杂度是非常标准的\(O(MlogM)\), 其中M为值域。

例题:为了可以带修改,使用根号重构。

注意:每次子集和dp的复杂度都是与值域相关,与实际数的个数无关(考虑优化)。

posted @ 2024-11-25 18:23  花子の水晶植轮daisuki  阅读(45)  评论(0)    收藏  举报
https://blog-static.cnblogs.com/files/zouwangblog/mouse-click.js