動態規劃 帶權值區間排程問題

2021-07-23 23:51:26 字數 3444 閱讀 6170

存在單一資源r,有n個需求,每個需求指定乙個開始時間bi與乙個結束時間ei,在時間區間[bi, ei]內該需求想要占用資源r,資源r一旦被占用則無法被其他需求利用。每個需求i帶有乙個權值vi,代表該需求被滿足之後能夠帶來的價值或者貢獻。如果兩個需求的時間區間不重疊,那麼它們是相容的。帶權值的區間排程問題即,對上面所描述的情境,求出一組相容子集s使得s中的區間權值之和最大。

舉個簡單例子,某一款網路遊戲近期舉辦了不少活動,玩家只要參加活動可以獲得相應的獎勵。每個活動都有對應的活動時間以及活動獎勵,玩家在乙個時間段之內只能參加乙個活動,並且必須完整地參加乙個活動才能獲取獎勵(即從活動開始到活動結束之間不能中斷去參加其他活動)。假設現在有n個活動,部分活動的時間區間有重疊。某位精通演算法的玩家希望通過計算確定合理的活動安排,使得自己獲取的獎勵最大化。

繼續以上面的遊戲活動為背景。假設將活動按照它們的結束時間ei從早到晚進行排序。e1 <= e2 <= … <= en。排序之後我們會得到如下所示的活動順序圖。

上圖中的標記表示活動名稱(活動1、活動2、…),vi表示參加活動i能夠收穫的獎勵,vi的值越大表示獎勵越多。下面定義圖中展示的p(j)。對於某個活動j,p(j)表示排在活動j之前的所有活動裡面離j最近並且相容的活動,如果沒有相容的活動,則p(j) = 0。例如p(3),從圖中可以看到,活動3前面只有活動2和活動1,而活動2跟活動3有區間重疊,不相容,只有活動1相容,因此p(3) = 1,代表活動3前面的所有活動中離它最近並且相容的活動是活動1。

我們要解決的問題是選擇一組最優活動組合使得所有活動獎勵之和最大。這裡再定義乙個概念,opt(n),表示從n個活動中能夠獲取到的最大獎勵。假設我們已經從中選取出一組最優活動組合,使得最大活動獎勵為opt(n-1)。現在要來考慮是否要參加活動n,這個時候有兩種選擇策略:

我們的目的是求取乙個最優的活動組合使得獎勵最大化,因此,只需要考慮上面兩種選擇哪一種的獎勵更多即可,獎勵越多的選擇越優秀。如果opt(p(n)) + vn的值更大,選擇參加活動n,放棄跟n衝突的其他活動。如果opt(n-1)更大,那麼放棄活動n,繼續保留原來選定的活動組合。於是我們很容易可以得到下面的遞推公式:

opt(j) = max(opt(p(j)) + vj, opt(j-1))
得到上面的公式之後,我們可以發現,求解乙個問題opt(j),可以通過求解它的子問題opt(j-1)來實現,於是我們做到了把求解乙個問題變成求解它的子問題。於是我們可以得到下面這個遞迴演算法:

opt(j):

if j == 0 then

return

0 else

return max(opt(p(j)) + vj, opt(j-1))

endif

上面的遞迴演算法是尾遞迴,可以使用迭代對其進行優化。使用乙個陣列(動態規劃的專業名詞叫做備忘錄)來記錄求解過的值,避免重複求解。改進之後可以得到下面這個迭代演算法,時間複雜度為o(n)。

opt[n] = 

for j = 1 to n:

opt[j] = max(opt(p(j)) + vj, opt(j-1))

endfor

該演算法已經可以幫助我們找到最大獎勵了。但僅知道乙個最大獎勵並沒有太大意義,我們更需要知道通過參加哪些活動來取得最大獎勵。因此,我們在計算最大活動獎勵的過程中,還需要記錄一下選取了哪些活動。

我們定義乙個記錄陣列s,繼續回到我們前面討論過的選擇活動n的時候面臨的兩種選擇。如果採取第一選擇,即參加活動n,我們便記錄s[n][0] = true,s[n][1] = p(n),代表我們在考慮活動n的時候選擇了參加活動n,搭配上前面中的最優組合。如果採取第二選擇,即放棄活動n,那麼我們記錄s[n][0] = false,s[n][1] = n-1,代表我們放棄活動n,此時活動的選擇情況還是與之前考慮活動n-1時候的情況一致。於是我們可以得到如下演算法:

opt[n] = 

初始化 s[n][2]

for j = 1 to n:

if opt(p(j)) + vj >= opt(j-1) then

opt[j] = opt(p(j)) + vj

s[j][0] = true

s[j][1] = p[j]

else

opt(j) = opt(j-1)

s[j][0] = false

s[j][1] = n-1

endif

endfor

通過上面演算法便可以得到想要的活動組合的記錄表了。然後反向搜尋一下記錄表便可得到最優的活動組合。

j = n

while j != 0:

if s[j][0] then

print j

endif

j = s[j][1]

endwhile

#include 

using

namespace

std;

const

int max_req_num = 30;

struct request ;

bool

operator

class dp

void init()

} // 預備,根據結束時間對所有請求排序,初始化陣列p

void prepare() }}

memset(record, 0, sizeof(record));

} // 動態規劃演算法

void solve() else

}} // 輸出結果

void output()

}}private:

request reqs[max_req_num + 1];

int requestnum;

int p[max_req_num + 1];

int optimal[max_req_num + 1];

int record[max_req_num + 1][2];

};int main()

輸入格式:

活動總數

活動1的開始時間、結束時間、貢獻價值

活動2的開始時間、結束時間、貢獻價值

...活動n的開始時間、結束時間、貢獻價值

輸入輸出示例:

6

0 4 2

1 6 4

5 7 4

2 9 7

8 10 2

8 11 1

[max value]:8

[activities]:

activity-5

activity-3

activity-1

問題 G 區間權值

時間限制 1 sec 記憶體限制 128 mb 提交 112 解決 49 提交 狀態 討論版 命題人 admin 題目描述 小bo有n個正整數a1.an,以及乙個權值序列w1 wn,現在他定義 現在他想知道 你只需要輸出答案對109 7取模後的值 輸入第一行乙個正整數n 第二行n個正整數a1.an ...

區間排程問題(貪心演算法與動態規劃)

給定n個活動,其中的每個活動ai包含乙個起始時間si與結束時間fi。設計與實現演算法從n個活動中找出乙個最大的相互相容的活動子集s。要求 分別設計動態規劃與貪心演算法求解該問題。其中,對貪心演算法分別給出遞迴與迭代兩個版本的實現。對於上述問題,可簡化為上方,當上乙個活動結束後,才可以進行下乙個活動,...

動態規劃 裝配線排程問題

這個問題是在演算法導論的動態規劃章節有提到,由於問題敘述起來太繁雜就直接省略。命名規則與書上的偽 是一致的,只是用c 具體語言實現了而已。動態規劃問題 裝配線排程問題 pragma once class asl include alspro.h include include using names...