C++值超程式設計
阿新 • • 發佈:2020-06-15
——永遠不要在OJ上使用值超程式設計,過於簡單的沒有優勢,能有優勢的編譯錯誤。
### 背景
2019年10月,我在學習演算法。有一道作業題,輸入規模很小,可以用打表法解決。具體方案有以下三種:
1. 執行時預處理,生成所需的表格,根據輸入直接找到對應項,稍加處理後輸出;
2. 一個程式生成表格,作為提交程式的一部分,後續與方法1相同,這樣就省去了執行時計算的步驟;
3. 以上兩種方法結合,編譯期計算表格,執行時直接查詢,即超程式設計(metaprogramming)。
做題當然是用方法1或2,但是超程式設計已經埋下了種子。時隔大半年,我來補上這個坑。
### 題目
北京大學OpenJudge 百練4119 複雜的整數劃分問題
#### 描述
將正整數 $n$ 表示成一系列正整數之和,$n = n_1 + n_2 + ... + n_k$,其中 $n_1 \geq n_2 \geq ... \geq n_k \geq 1$,$k \geq 1$。正整數 $n$ 的這種表示稱為正整數 $n$ 的劃分。
#### 輸入
標準的輸入包含若干組測試資料。每組測試資料是一行輸入資料,包括兩個整數 $N$ 和 $K$。( $0 \le N \leq 50$,$0 \le K \leq N$ )
#### 輸出
對於每組測試資料,輸出以下三行資料:
第一行: $N$ 劃分成 $K$ 個正整數之和的劃分數目
第二行: $N$ 劃分成若干個不同正整數之和的劃分數目
第三行: $N$ 劃分成若干個奇正整數之和的劃分數目
#### 樣例輸入
```
5 2
```
#### 樣例輸出
```
2
3
3
```
#### 提示
第一行: 4+1,3+2
第二行: 5,4+1,3+2
第三行: 5,1+1+3,1+1+1+1+1+1
### 解答
標準的動態規劃題。用`dp[c][i][j]`表示把`i`分成`c`個正整數之和的方法數,其中每個數都不超過`j`。
第一行。初始化:由 $i \leq j$ 是否成立決定`dp[1][i][j]`的值,當 $i \leq j$ 時為`1`,劃分為 $i = i$,否則無法劃分,值為`0`。
遞推:為了求`dp[c][i][j]`,對 $i = i_1 + i_2 + ... + i_c$,$i_1 \geq i_2 \geq ... \geq i_c$ 中的最大數 $i_1$ 分類討論,最小為 $1$,最大不超過 $i - 1$,因為 $c \geq 2$,同時不超過 $j$,因為定義。最大數為 $n$ 時,對於把 $i - n$ 分成 $c - 1$ 個數,每個數不超過 $n$ 的劃分,追加上 $n$ 可得 $i$ 的一個劃分。$n$ 只有這些取值,沒有漏;對於不同的 $n$,由於最大數不一樣,兩個劃分也不一樣,沒有多。故遞推式為:
$$dp[c][i][j] = \sum_{n=1}^{min\{i-1,j\}}dp[c-1][i-n][n]$$
`dp[K][N][N]`即為所求`ans1[K][N]`。
第二行。可以把遞推式中的`dp[c - 1][i - n][n]`修改為`dp[c - 1][i - n][n - 1]`後重新計算。由於只需一個與`c`無關的結果,可以省去`c`這一維度,相應地改變遞推順序,每輪累加。
另一種方法是利用已經計算好的`ans1`陣列。設 $i = i_1 + i_2 + ... + + i_{c-1} + i_c$,其中 $i_1 \ge i_2 \ge ... \ge i_{c+1} \ge i_c \ge 0$,則 $i_1 - \left( c-1 \right) \geq i_2 - \left( c-2 \right) \geq ... \geq i_{c-1} - 1 \geq i_c \ge 0$,且 $\left( i_1 - \left( c-1 \right) \right) + \left( i_2 - \left( c-2 \right) \right) + ... + \left( i_{c-1} - 1 \right) + \left( i_c \right) = i - \frac {c \left( c-1 \right)} {2}$,故把`i`劃分成`c`個不同正整數之和的劃分數目等於`ans[c][i - c * (c - 1) / 2]`,遍歷`c`累加即得結果。
第三行。想法與第二行相似,也是找一個對應,此處從略。另外,數學上可以證明,第二行和第三行的結果一定是一樣的。
```
#