周賽333。這題有夠難,根本是hard等級的,最近真的越來越誇張。

題目

輸入正整數陣列nums。

如果nums的一個子集合的乘積是無平方因子數,則該子集合為無平方子集

無平方因子數指的是不能被1以外的平方數所整除的整數。

求nums總共有多少非空子集合是無平方子集。答案可能很大,先模10^9+7後回傳。

解法

題目要的是子集乘積不可被1以外的平方數整除,這句話其實等價於每個質因數只能出現一次

而nums[i]最大為30,所以質因數p只會有[2,3,5,7,11,13,17,19,23,29]共十種。這些質因數的冪集共有2^n種集合,扣掉空集合剩下(2^n)-1個。

我們可以把每個質因數映射到不同的位元上,以bitmask表示那些質因數出現過。如”1101”代表子集[2,3,7]。
之後靠dp求出各組合出現的次數,一邊計算一邊更新答案。但有些數字一開始就有重複的質因數,如[4,8,12,25]等,需要特別判定不處理。

維護雜湊表dp,表示各組合出現次數,並初始化dp[0]=1,因為空集合永遠只有一種。
遍歷nums中的數字n,若合法則遍歷已出現過的組合k,若n與k沒有共通的質因數,則n可以和所有的k組成dp[k]個新的子集合,將dp[k]加入答案。為避免重複計算,要等處理完所有組合k後,再將新產生的組合加回dp中。

質數有P=10種,共2^P種組合,每次處理nums[i]都需要遍歷,時間複雜度O(N * 2^P)。dp只需要兩個雜湊表保存所有組合,空間複雜度O(2^P)。

class Solution:
    def squareFreeSubsets(self, nums: List[int]) -> int:
        MOD=10**9+7
        p=[2,3,5,7,11,13,17,19,23,29]
        
        def ok(n):
            for x in [4,9,16,25]:
                if n%x==0:
                    return False
            return True
        
        dp=Counter()
        dp[0]=1
        ans=0
        
        for n in nums:
            if not ok(n):continue
            t=Counter()
            mask=0
            for i in range(len(p)):
                if n%p[i]==0:
                    mask|=(1<<i)
                    
            for k,v in dp.items():
                if not mask&k:
                    t[mask|k]+=v
                    ans=(ans+v)%MOD
            dp+=t
                
        return ans