騰訊 小Q的歌單

最近開始多看一看名企曾經出過的編程題。今天看了一個動態規劃的題,非常受用。題目從牛客上摘一下。

鏈接:https://www.nowcoder.com/questionTerminal/f3ab6fe72af34b71a2fd1d83304cbbb3
來源:牛客網

小Q有X首長度爲A的不同的歌和Y首長度爲B的不同的歌,現在小Q想用這些歌組成一個總長度正好爲K的歌單,每首歌最多只能在歌單中出現一次,在不考慮歌單內歌曲的先後順序的情況下,請問有多少種組成歌單的方法。

輸入描述:

每個輸入包含一個測試用例。
每個測試用例的第一行包含一個整數,表示歌單的總長度K(1<=K<=1000)。
接下來的一行包含四個正整數,分別表示歌的第一種長度A(A<=10)和數量X(X<=100)以及歌的第二種長度B(B<=10)和數量Y(Y<=100)。保證A不等於B。

輸出描述:

輸出一個整數,表示組成歌單的方法取模。因爲答案可能會很大,輸出對1000000007取模的結果。

示例1

輸入

5
2 3 3 3

輸出

9

一道典型的動態規劃問題。一開始想用左老師的換錢方法數問題套用,但是這裏的問題是規定了兩種歌的數量上限,自己寫出來的是不限次數且同類中各個元素都一樣的版本。看了牛客一個C++的帖子以後豁然開朗,寫了一個python版本的,示例過了。至少把思路留一下:

K = int(input())
[A, X, B, Y] = list(map(int, input().split(' ')))

if X == 0 and Y == 0:
    print(1)

mod = 1000000007
dp = [[0 for col in range(K+1)] for row in range(X+Y+1)]
p = [0 for col in range(X+Y+1)]
for i in range(1, X+1):
    p[i] = A
for i in range(X+1, X+Y+1):
    p[i] = B

dp[0][0] = 1  # 初始化第一行,在沒有紙幣的時候,形成總和爲0的有1種辦法(就是都不取);總和爲其他數值的,都無法達到,有0種辦法
for i in range(1, K+1):
    dp[0][i] = 0

for i in range(1, X+Y+1):
    for j in range(0, K+1):
        if j >= p[i]:  # 當前要湊的這個數額j,可以由至少一個p[i]來完成
            dp[i][j] = (dp[i-1][j] + dp[i-1][j-p[i]]) % mod  # 兩部分:第一部分,是指本行(i)一個都不取,照搬i-1時的方法數;
                                                                # 第二部分,指的是本行多用一個,則i-1行少用一個時的方法數
        else:
            dp[i][j] = dp[i-1][j] % mod

print(dp[X+Y][K])

關鍵點是dp二維列表的含義。每一行代表用的歌的個數,而它對應的長度在p列表裏保存,也就是說有一個簡單的對應關係(或者說不同的歌代表不同的行)。dp的每一列代表能湊成多長的總長。因此,行是X+Y+1行,列是K+1行,最後取的時候找dp[X+Y][K]就是要的答案了。

在這種動態規劃外,一開始還看到網上普遍用的一種方法。這個矩陣構造得讓我覺得有問題,不是動態規劃的矩陣,到現在我也覺得這不像是dp方法。方法重點是利用兩種歌其中一個的範圍,用全排列的公式將所有相乘的結果相加。而全排列,是構造了楊輝三角矩陣,再查詢這個矩陣得來的。多數網上方法都說這個也是動態規劃,我不是很認同。一種形式的python代碼如下:

# -*- coding:utf-8 -*-
mod = 1000000007
K = int(input())
A, X, B, Y = list(map(int, input().split()))
 
arr = [[0 for i in range(101)] for j in range(101)]
arr[0][0] = 1
for i in range(1,101):
    arr[i][0] = 1
    for j in range(1,101):
        arr[i][j] = (arr[i-1][j-1]+arr[i-1][j]) % mod
 
ans = 0
for i in range(X+1):
    if (i*A <= K and (K-A*i) % B == 0 and (K-A*i)//B <= Y):
        ans = (ans +(arr[X][i]*arr[Y][(K-A*i)//B]) % mod) % mod
print(ans)

其中在最後輸出結果的時候,就是同時卡了幾個條件,恰好能湊成K,並且兩種歌的數量不超限而已。方法是很好很巧妙的,可以學習借鑑一下,不過這真是dp嗎?還望懂行的大佬指教一二。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章