1. 程式人生 > >狀態壓縮·一(狀態壓縮DP)

狀態壓縮·一(狀態壓縮DP)

pos weight i+1 else urn footer 分享 pre 同時

描述

小Hi和小Ho在兌換到了喜歡的獎品之後,便繼續起了他們的美國之行,思來想去,他們決定乘坐火車前往下一座城市——那座城市即將舉行美食節!

但是不幸的是,小Hi和小Ho並沒有能夠買到很好的火車票——他們只能夠乘坐最為破舊的火車進行他們的旅程。

不僅如此,因為美食節的吸引,許多人紛紛踏上了和小Hi小Ho一樣的旅程,於是有相當多的人遭遇到了和小Hi小Ho一樣的情況——這導致這輛車上的人非常非常的多,以至於都沒有足夠的位置能讓每一個人都有地方坐下來。

小Hi和小Ho本著禮讓他們的心情——當然還因為本來他們買的就是站票,老老實實的呆在兩節車廂的結合處。他們本以為就能夠這樣安穩抵達目的地,但事與願違,他們這節車廂的乘務員是一個強迫癥,每隔一小會總是要清掃一次衛生,而時值深夜,大家都早已入睡,這種行為總是會驚醒一些人。而一旦相鄰的一些乘客被驚醒了大多數的話,就會同乘務員吵起來,弄得大家都睡不好。

將這一切看在眼裏的小Hi與小Ho決定利用他們的算法知識,來幫助這個有著強迫癥的乘務員——在不與乘客吵起來的前提下盡可能多的清掃垃圾。

小Hi和小Ho所處的車廂可以被抽象成連成一列的N個位置,按順序分別編號為1..N,每個位置上都有且僅有一名乘客在休息。同時每個位置上都有一些垃圾需要被清理,其中第i個位置的垃圾數量為Wi。乘務員可以選擇其中一些位置進行清理,但是值得註意的是,一旦有編號連續的M個位置中有超過Q個的位置都在這一次清理中被選中的話(即這M個位置上的乘客有至少Q+1個被驚醒了),就會發生令人不愉快的口角。而小Hi和小Ho的任務是,計算選擇哪些位置進行清理,在不發生口角的情況下,清掃盡可能多的垃圾。

提示一:無論是什麽動態規劃,都需要一個狀態轉移方程!

提示二:好像什麽不對勁?狀態壓縮哪裏去了?

小Hi面對這個問題也是不慌不忙,反倒決定借此機會讓小Ho學習一下狀態壓縮動態規劃,於是推了推一旁仍然暈暈乎乎的小Ho,問道:“小Ho,你說這樣的問題能否使用動態規劃進行解決?”

小Ho思索了一番,在心中默默將這個問題與背包問題進行類比後,道:“我覺得似乎不可以,這個問題和背包問題其實是很類似的,不過背包問題對於選取物品的限制是總重量不能超過一定數額。但在這裏卻是要求不能夠在連續的一段位置中選取太多,如果我仍然以編號從小到大劃分階段,單單以best(i)表示當前已經決定了編號為1..i的位置是否選取的情況下最多可以清掃的垃圾數量的話,因為我不知道之前具體的選取方案,我是沒有辦法判斷當前這個位置能否進行選取的,這便是違反了動態規劃狀態定義的無後效性!

小Hi點了點頭表示贊同,但隨即繼續問道:”在背包問題中,正如我們之前經歷時所說,我們是通過將best(i)變成best(i, j),增加一個量——當前已經選取物品的總重量j到狀態中,從而能夠判斷當前是否能夠繼續選取物品,那麽在這裏,你覺得我們需要添加什麽樣的量才能夠達成我們的目的呢?”

這個問題頓時難倒了小Ho,但他也不是輕易放棄的性格,便拿出紙筆開始寫寫畫畫:“正如我之前所說,我不知道之前具體的選取方案,我是肯定沒有辦法判斷當前這個位置能否進行選取的!那麽我需要做的事情無非就是在狀態中記錄之前的選取方法,並且這些記錄需要能夠讓我推算出當前這個位置的垃圾是否能被清掃而不引起口角!”

思路一旦清晰,各種想法便接踵而至,小Ho思索片刻便得出了結論:“如果我將之前每一個位置是否選取的信息都存儲下來的話,那麽到了決定最後一個位置的時候,最壞情況下我就有2^(N-1)種可能的狀態,這個是我所不能接受的,但是我真的需要這麽多的信息麽?”

“不需要!”小Ho說道:“我只需要知道我之前的M-1個位置中選取了多少個位置就可以了!如果這個數目小於Q,那麽我當前就有兩種決策方案——選與不選,不然就就只有不選這一種方案。”

技術分享圖片

小Hi聽聞此言,皺了皺眉頭,問道:“那你的狀態難道就要定義成best(i, j)表示當前已經決定了編號為1..i的位置,並且從i-M+1 ... i-1這M-1個位置中已經選取了j個位置的情況下最多可以清掃的垃圾數量麽?”

小Ho剛想稱是,卻想道小Hi不會無緣無故的問這種問題,於是仔細考慮,頓時發現其中不對:“狀態固然是可以了,但是卻沒有辦法進行轉移,best(i, j)的下一步肯定是某個best(i+1, k),但是因為無法知曉i-M+1這個位置究竟是否在j個選取的位置中,所以是根本沒有辦法計算k的!

“而一旦記錄了i-M+1這個位置是否選取了的話,我就還需要記錄i-M+2這個位置——因為在best(i+1, k)中它便是(i+1)-M+1的這個位置,以此類推,也就是說我不能夠光記錄從i-M+1 ... i-1 這M-1個位置中選取了多少個位置,我還要將具體選擇了哪些位置都一一記錄下來!”小Ho思考道:“那我便只有如此定義狀態了——以best(i, p1, p2, p3, ... , pM-1)表示當前已經決定了編號為1..i的位置,並且第(i-j+1)個位置是否選取用pj進行記錄(0表示未選取,1表示選取)的情況下最多可以清掃的垃圾數量!

聽完小Ho新的想法,小Hi終於點了點頭,但也沒放棄繼續考校小Ho:“那你準備如何轉移狀態?”

“這個簡單,我只需要統計p1..pM-1之和S——即選取的位置總數,並且根據這個數目進行決策!”小Ho說罷在紙上寫出一個公式。

技術分享圖片

“那具體的計算順序呢?”小Hi也是將每個步驟都問的詳詳細細的。

小Ho張口便道:“這個容易,因為每次轉移都是從i向i+1進行的,所以我只需要按照i從小到大的順序進行計算就可以了!”

在得出結論之後,小Ho便拿出筆記本開始寫程序,寫著寫著便註意到:“這個M是根據輸入來的,那麽我怎麽開數組呢!難道要使用一些動態的方法?這樣也未免太過復雜了吧,更何況即使我動態的開了數組,我也沒有很好的方法來枚舉這些位置,難道要寫一大串的條件分支語句?”

思索無奈之下,只能夠去詢問小Hi,小Hi仿佛早就預料到了這個情況,掏出一張草稿紙來,寫下了一個長度為5的01串10101,問道:“你看這是什麽?”

“一個2進制串?我算算……等於21?”小Ho耿直的算了出來。

小Hi笑了笑“如果我說這便是M=6的情況下,以第一個01來表示你狀態中的p1,第二個01來表示你狀態中的p2,並依次類推,那麽我是否可以用(i, 21)來表示你的(i, 1, 0, 1, 0, 1)這樣一個狀態呢?”

小Ho頓時恍然大悟:“是了!既然是01串,那麽我就將這M-1個01視作一個二進制數又有何不可!這樣一來,我的狀態和狀態轉移方程豈非可以這樣定義?”

技術分享圖片

“是的!這便是所說的狀態壓縮,它在處理一些變長/變維度的狀態時時非常有效的,同時也可以利用位運算來優化代碼,方便計算!”小Hi適時的做了總結。

“嗯嗯!我這便去寫~”

輸入

每個測試點(輸入文件)有且僅有一組測試數據。

每組測試數據的第一行為三個正整數N、M和Q,意義如前文所述。

每組測試數據的第二行為N個整數,分別為W1到WN,代表每一個位置上的垃圾數目。

對於100%的數據,滿足N<=1000, 2<=M<=10,1<=Q<=M, Wi<=100

輸出

對於每組測試數據,輸出一個整數Ans,表示在不發生口角的情況下,乘務員最多可以清掃的垃圾數目。

Sample Input
5 2 1
36 9 80 69 85 
Sample Output
201
 1 /*
 2 狀態壓縮DP 
 3 問題 連續的1到n個位置,問在連續m個位上清掃垃圾不打擾超過q位乘客的情況下,最多能清掃多少垃圾
 4 抽象問題 在前i個位置裏,連續m個位置上清掃垃圾不打擾超過q位乘客的情況下,最多能清掃多少垃圾
 5 變量 i,m個位置上狀態(清理為1,不清理為0),最多能清理多少垃圾
 6 定義狀態 以best[i][p1,p2,p3,...pm-1]表示在前i個位置裏,連續m個位置上清掃垃圾不打擾超過q位乘客的情況下,最多能清掃多少垃圾
 7 並且以pj(j=1,2,3,..m-1)表示對應位置上是否清理,1表示清理,0表示不清理
 8 
 9 由於第二個變量長度及位數不確定,用數組模擬實現較為麻煩,所以將其狀態壓縮為一個十進制數(巧妙的看成m位的二進制數)
10  
11 狀態轉移 當枚舉的j裏二進制數中1的個數小於等於q時,表示當前位置i可以清理,可以清理表示可清可不清
12 當j的末尾位置為1,就是決定清理,則best[i][j]為前i-1個位置裏清掃和不清掃的中的最大值加上當前位置垃圾量
13 即best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]) + w[i]; 
14 當j的末尾位置為0,就是決定不清理,則best[i][j]為前i-1個位置裏清掃和不清掃的中的最大值(有點更新的意思) 
15 即best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]);
16 當枚舉的j裏二進制數中1的個數大於q時,表示當前位置只能是不清理,best[i][j]也為前i-1個位置裏清掃和不清掃的中的最大值
17 即best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]); 
18 
19 具體實現時,i推出i+1,則i遞增
20         j根據枚舉的二進制數的從小到大,也是遞增 
21 */
22 #include<stdio.h>
23 #include<string.h>
24 int n,m,q;
25 int w[1010];
26 int best[1010][1050];
27 int max(int x, int y){
28     return x > y ? x : y;
29 }
30 int one_num(int x);
31 int main()
32 {
33     int i,j;
34     while(scanf("%d%d%d",&n,&m,&q) != EOF)
35     {
36         for(i=1;i<=n;i++){
37             scanf("%d",&w[i]);
38         }
39         memset(best,0,sizeof(best));
40         for(i=1;i<=n;i++){
41             for(j=0;j < (1<<m);j++){
42                 /*if(one_num(j) <= q){
43                     if(j & 1)
44                     best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]) + w[i];
45                     else
46                     best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]);
47                 }
48                 else
49                 best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]);*/
50                 best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]);
51                 if(one_num(j) <= q){
52                     if(j & 1)
53                         best[i][j] += w[i];
54                 } 
55             }
56         }
57         int ans=-1;
58         for(i=0;i< 1<<m;i++){
59             if(ans < best[n][i])
60                 ans=best[n][i];
61         }
62         printf("%d\n",ans);
63     }
64     return 0;
65 } 
66 int one_num(int x)
67 {
68     int num=0;
69     while(x){
70         if(x & 1)
71             num++;
72         x >>= 1;
73     }
74     return num;
75 }

狀態壓縮·一(狀態壓縮DP)