LintCode1772 子集数量(划分原集合+状压dp)
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LintCode1772 子集数量(划分原集合+状压dp)相关的知识,希望对你有一定的参考价值。
这篇题解仅作为https://www.lintcode.com/problem/1772/discuss/2519的一个补充注释
我首先想到的是一个错误的做法
首先用图论描述。i和2i,i和3i连无向边,那么一个点和邻居不能都选。但是还做不了。但我们想到dag上dp很常见,所以考虑赋予方向,比如小连大。这样卷得到dag,设dp【i】【0/1】表示i不选和选的方案数。
转移:i选了,那么邻居都只能是不选;否则就是选和不选加起来。两种情况每个邻居都是连乘。现在没电脑打不了转移方程
错误原因可能是,树的独立集dp可行,但DAG的独立集dp不可行。
直接对整体进行考虑,十分困难。那么我们考虑先对数字进行划分,再分别处理。枚举b是1~n的所有没有素因子2和3的数,则这些集合:{2^i*3^j*b}
是1 ~ n的一个划分。因为b没有2和3,所以各个集合的元素两两无关,可以分别决策。设b[1] ~ b[x]分别有方案y[1] ~ y[x]种,则ans=y[1]*...*y[m]
对于一个b,我们按2^i
的不同i
值来划分当前集合,每个i
对应一组。则选取元素的过程可以分为大约log2(n)
个阶段。设第i
组选取的3^j
的集合为S。那么我们只需要对第i-1
组选取的S0和第i
组选取的S进行讨论。比如:S0=1,S=5,i=1
,表示第0组选取了{2^0*3^0} = {1}
,第1组选取了{2^1*3^0,2^1*3^2} = {2,18}
。
定义dp[i][S]
为前i-1
组已经选好,第i
组选取情况为S的方案数。一个基本性质是,dp[i][S1]
和dp[i][S2]
的交集为空。显然,对满足S&S0==0
的S0,有dp[i][S] += dp[i-1][S0]
。
此外,S和S0里面的最大元素要<=n。即b*(2**i)*(3**l2[S]) <= n
和b*(2**(i-1))*(3**l2[S]) <= n
。其中l2[S]
表示S的高位的下标(0-indexed)。
答案就是
d
p
[
l
i
m
2
]
[
0
]
=
∑
S
>
=
1
d
p
[
l
i
m
2
−
1
]
[
S
]
dp[lim2][0]=\\sum_{S>=1}dp[lim2-1][S]
dp[lim2][0]=S>=1∑dp[lim2−1][S]
lim2表示最小的满足b*(2**i) > n
的i
值。以上等式,是由转移方程得到的推论。
class Solution:
"""
@param n: the range of element is in [1,n]
@return: return the number of good collections
"""
def numberOfCollections(self, n):
# write your code here
mod = int(1e9 + 7)
l2 = [-1 for i in range(n+1)]
for i in range(1,n+1): l2[i] = l2[i>>1]+1
ans = 1
for b in range(1,n+1):
if b % 2 == 0 or b % 3 == 0: continue
lim2,lim3 = 0,0
while b*(2**lim2) <= n: lim2 += 1
while b*(3**lim3) <= n: lim3 += 1
dp = [[0 for j in range((1<<lim3)+1)] for i in range(lim2+1)]
can = []
for S in range(1<<lim3):
if (S & (S >> 1)) == 0:
dp[0][S] = 1
can.append(S)
for i in range(1,lim2+1):
for S in can:
if i < lim2 and b*(2**i)*(3**l2[S]) > n: break
for S0 in can:
if b*(2**(i-1))*(3**l2[S]) > n: break
if (S&S0) == 0:
dp[i][S] = (dp[i][S]+dp[i-1][S0]) % mod
ans = ans * dp[lim2][0] % mod
return ans
s = Solution()
print(s.numberOfCollections(4))
print(s.numberOfCollections(9))
print(s.numberOfCollections(10))
print(s.numberOfCollections(100))
print(s.numberOfCollections(10000))
print(s.numberOfCollections(100000))
以上是关于LintCode1772 子集数量(划分原集合+状压dp)的主要内容,如果未能解决你的问题,请参考以下文章
Luogu4221 WC2018州区划分(状压dp+FWT)
lintcode 734. 形式为a^i b^j c^k的子序列数量 题解