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]) <= nb*(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>=1dp[lim21][S]
lim2表示最小的满足b*(2**i) > ni值。以上等式,是由转移方程得到的推论。

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)的主要内容,如果未能解决你的问题,请参考以下文章

lintcode17/18Subset, SubsetII

Luogu4221 WC2018州区划分(状压dp+FWT)

lintcode 734. 形式为a^i b^j c^k的子序列数量 题解

Lintcode17 Subsets solution 题解

等和的分隔子集(DP)

拆分集合为相等的子集合(第1届第1题)