王曉東老師編著的《計算機演算法設計與分析》
5.12
節以「連續郵資問題」為例展示了回溯法的應用。講解比較簡略,對於搜尋出一張新的郵票面值後如何更新最大連續郵資區間這一點沒有過多的說明。以下是自己對於這一節學習的一點筆記。
實際上,關於剛才所說的更新最大連續郵資區間的方法,可以歸結到一種「等價類」的思想。與此相似的還有《程式設計之美》中「陣列分割問題」的解法三,《程式設計之美》中「找符合條件的整數」的最後一種演算法,
joj 1903
,joj 1278
這些題目等等。以下先從頭到尾把連續郵資問題複習一遍,然後小結一下這種「等價類」的方法。
連續郵資問題:某國家發行了
n種不同面值的郵票,並且規定每張信封上最多隻允許貼
m張郵票。連續郵資問題要求對於給定的n和
m的值,給出郵票面值的最佳設計,在
1張信封上貼出從郵資
1開始,增量為
1的最大連續郵資區間。例如,當
n=5和
m=4時,面值為的5
種郵票可以貼出的最大連續郵資區間是1到
70.當然是用回溯法。搜尋結點的狀態應該是已經確定的郵票面值
(各不相同並且總數不超過
n)和它們能夠貼出的最大連續郵資區間,以此來列舉下乙個可能的郵票面值。因此,很自然地,使用原書中的識別符號,陣列
x記錄當前已經確定的郵票面值,整數
r表示當前使用不超過
m張郵票能貼出的最大連續郵資區間。對於第
i 層的結點,
x[1…i]
表示當前已經有i 個
面值確定,
r表示由
x[1…i]
能貼出的最大連續區間,現在,要想把第
i 層的結點往下擴充套件,有兩個問題需要解決:一,哪些數有可能成為下乙個的郵票面值,即
x[i+1]
的取值範圍是什麼;二,對於乙個確定的
x[i+1]
,如何更新
r的值讓它
表示x[1…i+1]
能表示的最大連續郵資區間。
第乙個問題很簡單,
x[i+1]
的取值要和前面
i 個數各不相同,最小應該是
x[i] + 1
,最大就是
r+1,否則
r+1沒有辦法表示。我們現在專注第二個問題。
第二個問題自己有兩種思路:一,計算出所有使用不超過m張
x[1…i+1]
中的面值能夠貼出的郵資,然後從
r+1開始逐個檢查是否被計算出來。二,從
r+1開始,逐個詢問它是不是可以用不超過m張
x[1…i+1]
中的面值貼出來。
兩種思路直接計算其計算量都是巨大的,需要借助動態規劃的方法。模仿
0-1揹包問題,假設
s(i)
表示x[1…i]
中不超過
m張郵票的貼法的集合,這個集合中的元素數目是巨大的,例如,只使用
1張郵票的貼法有
c(i+1-1,1)
=c(i,1)=i
種,使用
2張郵票的貼法有
c(i+2-1,2)=c(i+1,2)=i*(i+1)/2
種,……,使用
m張郵票的貼法有
c(i+m-1, m)
種,其中
c(n,r)表示n
個 元素中取r個
元素的組合數。於是,
s(i)
中的元素的數目總共有
c(i+1-1, 1) + c(i+2-1,2)+ … + c(i+m-1,m)個 。
s(i)
中的每個元素就是一種合法的貼法,對應乙個郵資。當前最大連續郵資區間為1到
r,那麼
s(i)
中每個元素的郵資是不是也在1到
r之間呢?不一定,比如
,當m=2
時,它能貼出來
8,但不能貼出來
7,這一點自己在寫**時犯了錯誤。總之,在搜尋時,一定要保持狀態的一致性,即當深度搜尋到第
i 層時,一定要確保用來儲存結點狀態的變數中儲存的一定是第
i 層的這個結點的狀態。言歸正傳,定義
s(i)
中元素的值就是它所表示的貼法貼出來的郵資,於是,可以把
s(i)
中的元素按照它們的值的相等關係分成
k類。第
j類表示
貼出郵資為
j的所有的貼法集合,用
t(j)
表示,t(j)
有可能是空集,例如對於
,t(7)
為空集,
t(8)=}
。此時有:
s( i
) = t(1) u t(2) u t(3) u … u t(k),u
表示兩個集合的並。
現在考慮
x[i+1]
加入後對當前狀態
s(i)
的影響。假設s是
s(i)
中的乙個元素,即
s表示一種合法的貼法,
x[i+1]對s
能貼出的郵資的影響就是
x[i+1]
的多次重複增加了
s能貼出的郵資。這樣說是因為有兩種情況不需要考慮:一, 從
s中去掉幾張郵票,把
x[i+1]
加進去,這沒有意義,因為從
s中去掉幾張郵票後
s就變成了
s(i)
中的另乙個元素
t,我們遲早會對t考慮
x[i+1]
的影響的。二,將
x[i+1]加入s
,同時再把
x[1]
也加入s(如果s
中還能再貼兩張郵票的話
),這也沒有意義,原因同一。所以,
x[i+1]對s
的影響就是,如果
s中貼的郵票不滿
m張,那就一直貼
x[i+1]
,直到s中有m
張郵票,這個過程會產生出很多不同的郵資,它們都應該被加入到
s(i+1)
中。因為s屬於
s(i)
,它也必定在某個
t(k)
中,而t(k)
中能產生出最多不同郵資的是
t(k)
中用的郵票最少的那個元素。至此,原書中的解法就完全出來了:用陣列
x記錄當前已經確定的郵票面值,用
r表示當前最大的連續郵資區間,用陣列
y表示用當前的面值貼出某個郵資所需要的最少的郵票數。狀態結點的轉換過程已經在上面說的非常清楚了。現在只差寫**了。**如下:
#include
#define
max_nm 10
#define
max_postage 1024
#define
inf 2147483647
intn, m;
intx[max_nm], ans[max_nm], y[max_postage], maxstamp, r;
/** backtrack(i)
表示x[0...i-1]這i
張郵票已經完全確定,
* 相應於
x[0...i-1]
的最大連續郵資區間
r和每種郵資所需要的
* 最少郵票張數
y[0...r]
也都確定,現在列舉
x[i]
* 的每個值,確定
x[i]
*/void
backtrack(
int i)
return ;}
backup_y = (
int *)malloc
(max_postage *
sizeof
(int
));for(
tmp= 0; tmp
< max_postage; tmp++) backup_y[tmp] = y[tmp];
backup_r = r;
for(
next = x[i - 1] + 1; next <= r + 1; next++)
/* update r */
while
( y[r + 1] < inf) r++;
backtrack(
i + 1);
/* restore */
r = backup_r;
for(
tmp= 0; tmp
< max_postage; tmp++) y[tmp] = backup_y[tmp];
}free
( backup_y
);}intmain()
用演算法教材上的蒙特卡羅方法估算該演算法解空間樹的結點數,得到以下結果:nm
解平均結點數54
:70619055
:126
2676256
:216
9469063
:521258764
:108
158364
連續郵資問題
假設某國家發行了n種不同面值的郵票,並且規定每張信封上最多隻允許貼m張郵票。連續郵箱問題要求對於給定的n和m,給出郵票面值的最佳設計,在1張信封上貼出從郵資1開始,增量為1的最大連續郵資區間。例如當n 5,m 4時,面值為1,3,11,15,32的5種郵票可以貼出郵資的最大連續區間是1到70。inc...
連續郵資問題
連續郵資問題 演算法設計 該問題是設計最佳的郵票面值,用來表示最大的區間 對於連續郵姿問題,用n元組x 1 n 表示n種不同的郵票面值並約定它們從小到大排列。整數r表示當前使用不超過m張郵票能貼出的最大連續郵資區間。x 1 1是唯一的選擇。此時最大連續郵資區間是 1 m 接下來x 2 的可能取值範圍...
連續郵資問題
假設國家發行了n種不同面值的郵票,並且 規定每張信封上最多隻允許貼m張郵票。連 續郵資問題要求對於給定的n和m的值,給出 郵票面值的最佳設計,在1張信封上可貼出 從郵資1開始,增量為1的最大連續郵資區 間。noip99 例如,當n 2 m 3時,如果面值分別為1 4,則在l 6之間的每乙個郵資值都能...