雙周賽117。我在那邊搞排容原理搞一輩子,都沒想到dp也可以做,還簡單的很。

題目

輸入整數n。

一個由小寫字母組成的字串s,如果排序後可以包含子字串”leet”,則稱為好的

例如:

  • “lteer”可以重排成”leetr”,是好的
  • “letl”重排後無法得到”leet”,不是好的

求有多少長度n的好的字串
答案可能很大,先模10^9+7後回傳。

解法

必要條件是一個l、一個t、兩個e。

對於一個長度n的字串,可以由長度n-1的字串加上任意一種字母而來。
字串的組成是決定選哪個字母加上去,因此考慮dp。

我們只在乎let三字母的出現次數。而且一旦滿足,再多也沒有意義。
換句話說,”leeet”和”lleettt”的效果都等價於”leet”,都是合法的。

定義dp(n,l,e,t):長度為n,且需要有l個l、e個e、t個t的字串,總共有幾種。
轉移方程式:dp(n) = dp(n-1)拿其他 + dp(n-1)拿l + dp(n-1)拿e + dp(n-1)拿t
base case:當n=0時,不能繼續選,如果let需求都滿足,則回傳1;否則不合法,回傳0。

dp狀態共有n*l*e*t個,其中l*e*t為視作常數。
每個狀態轉移4次。
時間複雜度O(n)。
空間複雜度O(n)。

class Solution:
    def stringCount(self, n: int) -> int:
        MOD=10**9+7
            
        @cache
        def dp(n,l,e,t):
            if n==0:
                return int(l+e+t==0)
            # take other
            res=dp(n-1,l,e,t)*23
            # take l
            res+=dp(n-1,max(0,l-1),e,t)
            # take e
            res+=dp(n-1,l,max(0,e-1),t)
            # take t
            res+=dp(n-1,l,e,max(0,t-1))
            return res%MOD
        
        return dp(n,1,2,1)

排容原理又來了,一樣是找出所有字串,扣掉不合法的。
字串總共有26^n種排列。

滿足以下任一可以使字串不合法:

  • 沒l
    l之外任選n次,即25^n
  • 沒t
    同上,25^n
  • 沒ee
    沒有任何e的情況同上,25^n
    但可以有一個e,其他位置填非e,即n*25^(n-1)

不合法的字串也就是三種條件的聯集。
根據排容原理:

A∪B∪C = A+B+C-A∩B-A∩C-B∩C+A∩B∩C

符合兩項條件的地方重複計算,需要扣除。

  • 沒l也沒t
    l,t以外任選n次,即24^n
  • 沒t且沒ee
    t,e以外任選n次,即24^n
    但可以有一個e,其他位置填非e,t,即n*24^(n-1)
  • 沒l且沒ee
    同上,n*24^(n-1)

最後補上三項條件皆滿足的部分。

  • 沒l且沒t且沒ee
    l,t,e以外任選n次,即23^n
    但可以有一個e,其他位置填非l,e,t,即n*23^(n-1)

答案記得要取餘數。

時間複雜度O(log n),在於快速冪求n次方。
空間複雜度O(1)。

class Solution:
    def stringCount(self, n: int) -> int:
        MOD=10**9+7
        tot=pow(26,n,MOD)
        
        # no "l"
        exclude=pow(25,n,MOD)
        # no "t"
        exclude+=pow(25,n,MOD)
        # no "ee"
        exclude+=pow(25,n,MOD)+n*pow(25,n-1,MOD)        
        
        # no "l" && no "t"
        exclude-=pow(24,n,MOD)
        # no "l" && no "ee"
        exclude-=pow(24,n,MOD)+n*pow(24,n-1,MOD)
        # no "t" && no "ee"
        exclude-=pow(24,n,MOD)+n*pow(24,n-1,MOD)
        
        # no "l" && no "t" && no "ee"     
        exclude+=pow(23,n,MOD)+n*pow(23,n-1,MOD)
        
        return (tot-exclude)%MOD

式子加起來變成這樣。
老實說還是DP寫比較快。

class Solution:
    def stringCount(self, n: int) -> int:
        MOD=10**9+7
        tot=pow(26,n,MOD)
        exclude=(
            pow(25,n,MOD)*3 + n*pow(25,n-1,MOD)  
            - (pow(24,n,MOD)*3 + n*pow(24,n-1,MOD)*2)
            + pow(23,n,MOD) + n*pow(23,n-1,MOD)
        )
        
        return (tot-exclude)%MOD