🔗 UVA-10721 Bar Codes

tags: 動態規劃(DP), 記憶化搜尋(Memoization)

範例程式碼已於UVA瘋狂程設(CPE) 上皆測試通過 (此題無 ZeroJudge )。

題意

條碼(bar code) 是由黑色和白色的 單元(unit) 組成,且由黑色的單元開始,若干個 unit 可以組成一個 長條(bar) ,而若干個 bar 又能組成一個 bar code

給定三個整數 n,k,mn, k, m,代表條碼的長度為 nnunit 、具有 kkbar 、每個 bar 的寬度最多為 mmunit ,求有多少種不同的條碼可以滿足條件,即求 BC(n,k,m)BC(n, k, m)

思路:動態規劃(DP)

來自 @灵茶山艾府

求解 BC(n,k,m)BC(n, k, m) 時,我們可以考慮第一個 bar 的寬度 xx,將問題分解成求解 BC(nx,k1,m)BC(n-x, k-1, m) 之子問題,也就是剩下 nxn-xunitsk1k-1bars ,每個 bars 最多 mmunits 時的答案。將這些子問題的答案加總即為 BC(n,k,m)BC(n, k, m) 的答案。

再來考慮 xx 的範圍,顯然 xx 不能超過目前可用的 unit 數量 nn,且每個 bar 的寬度最多為 mmunits ,因此 xx 的範圍為 [1,min(n,m)][1, \min(n, m)]

由於每次的顏色必須和上次不同,且開頭也只能是黑色,因此在考慮每個子問題時,都只需當作只有一種顏色即可。

最後來寫邊界條件:

  1. 對於 n=0n=0 時,若 k=0k=0 代表能夠滿足條件,返回 11;否則代表 bar 的數量不夠,返回 00
  2. 對於 k=1k=1 時,若 nmn \leq m 代表能夠滿足條件,返回 11;否則代表 bar 的寬度超過限制,返回 00

複雜度分析

  • 時間複雜度:O(N3)O(N^3),其中 NNnn 的最大值,對於每個詢問。
    • 由於在同一個詢問中 mm 值固定,因此總共有 O(N2)O(N^2) 個子問題,求解每個子問題的時間複雜度為 O(N)O(N)
    • 由於 k,mnk, m \leq n ,故考慮 nn 的最大值即可。
  • 空間複雜度:O(N3)O(N^3),用於存儲子問題的答案。
  • 使用 Top-Down 的方式實作動態規劃,並使用 functools.cache 進行 記憶化搜尋(Memoization)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import *

@cache
def BC(n, k, m):
if n == 0: # 沒有unit了,若還有bar則不滿足
return 1 if k == 0 else 0
if k == 1: # 只有1個bar,若滿足限制條件則有1種可能
return 1 if n <= m else 0
res = 0
for x in range(1, min(n, m)+1):
res += BC(n-x, k-1, m) # 剩下n-x個units, k-1個bars, 每個bar最多還是m個units
return res

while True:
try:
n, k, m = map(int, input().split())
except EOFError:
break
print(BC(n, k, m))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 55;
#define endl '\n'

LL dp[N][N][N];

LL BC(int n, int k, int m) {
if (dp[n][k][m] != -1) return dp[n][k][m]; // memoization
if (n == 0) return k == 0; // 沒有unit了,若還有bar則不滿足
if (k == 1) return n <= m; // 只有1個bar,若滿足限制條件則有1種可能
LL res = 0;
for (int x = 1; x <= min(n, m); ++x) {
res += BC(n-x, k-1, m); // 剩下n-x個units, k-1個bars, 每個bar最多還是m個units
}
dp[n][k][m] = res;
return res;
}

int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int n, k, m;
memset(dp, -1, sizeof(dp));
while (cin >> n >> k >> m) {
cout << BC(n, k, m) << endl;
}
return 0;
}

類題


寫在最後

Cover photo is remixed from @SuiginToxic, thanks for their work!