最近開始多看一看名企曾經出過的編程題。今天看了一個動態規劃的題,非常受用。題目從牛客上摘一下。
鏈接: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嗎?還望懂行的大佬指教一二。