HDU 2829 Lawrence 斜率優化DP 題解與分析
● 本題解會有詳細的分析,適合初學者閱讀 |
⚠ 超長題目分析警告 |
原題
Problem Description
T. E. Lawrence was a controversial figure during World War I. He was a British officer who served in the Arabian theater and led a group of Arab nationals in guerilla strikes against the Ottoman Empire. His primary targets were the railroads. A highly fictionalized version of his exploits was presented in the blockbuster movie, “Lawrence of Arabia”.
You are to write a program to help Lawrence figure out how to best use his limited resources. You have some information from British Intelligence. First, the rail line is completely linear—there are no branches, no spurs. Next, British Intelligence has assigned a Strategic Importance to each depot—an integer from 1 to 5. A depot is of no use on its own, it only has value if it is connected to other depots. The Strategic Value of the entire railroad is calculated by adding up the products of the Strategic Values for every pair of depots that are connected, directly or indirectly, by the rail line. Consider this railroad:
Its Strategic Value is 45 + 41 + 42 + 51 + 52 + 12 = 49.
Now, suppose that Lawrence only has enough resources for one attack. He cannot attack the depots themselves—they are too well defended. He must attack the rail line between depots, in the middle of the desert. Consider what would happen if Lawrence attacked this rail line right in the middle:
The Strategic Value of the remaining railroad is 45 + 12 = 22. But, suppose Lawrence attacks between the 4 and 5 depots:
The Strategic Value of the remaining railroad is 51 + 52 + 1*2 = 17. This is Lawrence’s best option.
Given a description of a railroad and the number of attacks that Lawrence can perform, figure out the smallest Strategic Value that he can achieve for that railroad.
Input
There will be several data sets. Each data set will begin with a line with two integers, n and m. n is the number of depots on the railroad (1≤n≤1000), and m is the number of attacks Lawrence has resources for (0≤m<n). On the next line will be n integers, each from 1 to 5, indicating the Strategic Value of each depot in order. End of input will be marked by a line with n=0 and m=0, which should not be processed.
Output
For each data set, output a single integer, indicating the smallest Strategic Value for the railroad that Lawrence can achieve with his attacks. Output each integer in its own line.
題目翻譯
Problem Description
歷史背景自行閱讀,與題目無關。。。光知道你要寫個程式幫助這個叫La的玄學人就行了。。。
首先,鐵路線是完全線性的——沒有分支和岔道。接下來,某玄學部門給每個車段分配了一個戰略價值(一個從1到5的整數)。車段本身沒有用處,只有與其他車段連線時才有價值。整個鐵路的戰略價值是通過將鐵路線直接或間接連線的每對車輛段的戰略價值乘積相加來計算的。想想這條鐵路:
其戰略價值為 4 ∗ 5 + 4 ∗ 1 + 4 ∗ 2 + 5 ∗ 1 + 5 ∗ 2 + 1 ∗ 2 = 49 4*5+4*1+4*2+5*1+5*2+1*2=49 4∗5+4∗1+4∗2+5∗1+5∗2+1∗2=49。
現在,假設La只有足夠的資源進行一次攻擊。他不能親自攻擊倉庫,因為他們的防禦太好了。他必須攻擊沙漠中央的火車站之間的鐵路線。想想如果勞倫斯襲擊了這條鐵路線中間會發生什麼:
剩餘鐵路的戰略價值為 4 ∗ 5 + 1 ∗ 2 = 22 4*5+1*2=22 4∗5+1∗2=22。但是,假設La襲擊了4號和5號倉庫:
剩餘鐵路的戰略價值為 5 ∗ 1 + 5 ∗ 2 + 1 ∗ 2 = 17 5*1+5*2+1*2=17 5∗1+5∗2+1∗2=17。這是La的最佳選擇。
給出一條鐵路的引數和La能發動的攻擊次數,編寫程式找出他能將該鐵路的戰略價值降低到最小的值。
Input
多組資料。
每組資料的第一行為兩個整數,n和m。n是鐵路上的車輛段數(1≤n≤1000),m是La有資源攻擊的次數(0≤m<n)。下一行是n個整數,每個整數從1到5,依次表示每個倉庫的戰略價值,當輸入的n、m都為0時,輸入結束。
Output
對於每組資料,輸出一個整數,表示La可以通過攻擊實現的鐵路的最小戰略價值。
題目分析
本題目需要前導知識:DP優化-斜率優化以及四邊形不等式優化(四邊形不等式暫時不討論)
但請放心:使用前導知識前,我們需要先通過一般手段解決這道題目。優化可以學完以後再做理解。
我們首先按照一般分析思路對題目進行分析:題目給出了一個長度為 n n n的序列,至多將序列分成 m + 1 m+1 m+1段。每段序列具有一個權值,權值的計算方法是閉區間內點權兩兩相乘之和。求整個序列權值最小和。
首先我們需要一個數組儲存點權,我們記為 v a l [ N ] val[N] val[N],為了後續計算方便,我們提前處理出字首和。(關於字首和不懂的請挪步:字首和與差分)
字首和我們記作 c n t [ N ] cnt[N] cnt[N],採用一層迴圈遞推到底求出各點字首和,至於為什麼要處理出字首和,請繼續閱讀:
由於本題是建立在區間的基礎上進行分析,因此我們不光要有點權,還應該處理出區間權值和,我們將閉區間 [ i , j ] [i,j] [i,j]的權值和記作 c o s t [ i ] [ j ] cost[i][j] cost[i][j],我們舉例來分析如何求區間權值。
設區間長度為
n
=
4
n = 4
n=4,我們以題目敘述示例中的
4
,
5
,
1
,
2
4,5,1,2
4,5,1,2作為各點權值,記作
v
a
l
[
1
]
,
v
a
l
[
2
]
,
v
a
l
[
3
]
,
v
a
l
[
4
]
val[1],val[2],val[3],val[4]
val[1],val[2],val[3],val[4],那麼
[
1
,
4
]
[1,4]
[1,4]區間的權值:
c
o
s
t
[
1
]
[
4
]
=
v
a
l
[
4
]
∗
v
a
l
[
3
]
+
v
a
l
[
4
]
∗
v
a
l
[
2
]
+
v
a
l
[
4
]
∗
v
a
l
[
1
]
+
v
a
l
[
3
]
∗
v
a
l
[
2
]
+
v
a
l
[
3
]
∗
v
a
l
[
1
]
+
v
a
l
[
2
]
∗
v
a
l
[
1
]
=
v
a
l
[
4
]
∗
(
v
a
l
[
1
]
+
v
a
l
[
2
]
+
v
a
l
[
3
]
)
+
v
a
l
[
3
]
∗
(
v
a
l
[
1
]
+
v
a
l
[
2
]
)
+
v
a
l
[
2
]
∗
v
a
l
[
1
]
\begin{aligned} cost[1][4] &= val[4] * val[3] + val[4] * val[2] + val[4] * val[1] + val[3] * val[2] + val[3] * val[1] + val[2] * val[1]\\ &=val[4] * (val[1] + val[2] + val[3]) + val[3]*(val[1] + val[2]) + val[2] * val[1]\\ \end{aligned}
cost[1][4]=val[4]∗val[3]+val[4]∗val[2]+val[4]∗val[1]+val[3]∗val[2]+val[3]∗val[1]+val[2]∗val[1]=val[4]∗(val[1]+val[2]+val[3])+val[3]∗(val[1]+val[2])+val[2]∗val[1]
同理,
[
1
,
3
]
[1,3]
[1,3]區間的權值為:
c
o
s
t
[
2
]
[
4
]
=
v
a
l
[
3
]
∗
v
a
l
[
2
]
+
v
a
l
[
3
]
∗
v
a
l
[
1
]
+
v
a
l
[
2
]
∗
v
a
l
[
1
]
=
v
a
l
[
3
]
∗
(
v
a
l
[
2
]
+
v
a
l
[
1
]
)
+
v
a
l
[
2
]
∗
v
a
l
[
1
]
\begin{aligned} cost[2][4] &= val[3] * val[2] + val[3] * val[1] + val[2] * val[1]\\ &=val[3]*(val[2] + val[1]) + val[2] * val[1]\\ \end{aligned}
cost[2][4]=val[3]∗val[2]+val[3]∗val[1]+val[2]∗val[1]=val[3]∗(val[2]+val[1])+val[2]∗val[1]
我們不難發現:當我們計算每段區間的權值時,會進行大量重複的計算。但是計算中重複的部分是固定的,因此,我們不妨將
[
1
,
4
]
[1,4]
[1,4]區間的計算式與
[
1
,
3
]
[1,3]
[1,3]區間計算式做差移項,可得:
c
o
s
t
[
1
]
[
4
]
=
v
a
l
[
4
]
∗
(
v
a
l
[
3
]
+
v
a
l
[
2
]
+
v
a
l
[
1
]
)
+
c
o
s
t
[
1
,
3
]
\begin{aligned} cost[1][4] &= val[4] * (val[3] + val[2] + val[1]) + cost[1,3]\\ \end{aligned}
cost[1][4]=val[4]∗(val[3]+val[2]+val[1])+cost[1,3]
這裡我們又發現,乘項
(
v
a
l
[
3
]
+
v
a
l
[
2
]
+
v
a
l
[
1
]
)
(val[3] + val[2] + val[1])
(val[3]+val[2]+val[1])具有重複的求和性質,於是我們就想到了字首和優化,避免重複進行這些無意義的加法。於是,上式再次被化簡:
c
o
s
t
[
1
]
[
4
]
=
v
a
l
[
4
]
∗
(
c
n
t
[
3
]
−
0
)
+
c
o
s
t
[
1
,
3
]
\begin{aligned} cost[1][4] &= val[4] * (cnt[3] - 0) + cost[1,3]\\ \end{aligned}
cost[1][4]=val[4]∗(cnt[3]−0)+cost[1,3]
為什麼是
c
n
t
[
3
]
−
0
cnt[3] - 0
cnt[3]−0?因為我們的舉例區間是從頭開始的,如果從
i
−
1
i-1
i−1開始,那麼到
j
j
j的字首和應表達為:
c
n
t
[
j
−
1
]
−
c
n
t
[
i
−
1
]
cnt[j-1] - cnt[i-1]
cnt[j−1]−cnt[i−1]
示例的值示意圖如下:
經過計算,我們得出的 c o s t cost cost陣列的值如下:
綜上所述,我們對區間進行推廣,可以得出任意區間
[
i
,
j
]
[i,j]
[i,j]的遞推式:
c
o
s
t
[
i
]
[
j
]
=
v
a
l
[
j
]
∗
(
v
a
l
[
j
−
1
]
+
v
a
l
[
j
−
2
]
+
.
.
.
+
v
a
l
[
i
]
)
+
v
a
l
[
j
−
1
]
∗
(
v
a
l
[
j
−
2
]
+
v
a
l
[
j
−
3
]
+
.
.
.
"
v
a
l
[
i
]
"
)
+
.
.
.
+
v
a
l
[
i
−
1
]
∗
v
a
l
[
i
]
=
v
a
l
[
j
]
∗
(
c
n
t
[
j
−
1
]
−
c
n
t
[
i
−
1
]
)
+
c
o
s
t
[
i
]
[
j
−
1
]
\begin{aligned} cost[i][j] &=val[j] * (val[j - 1] + val[j - 2] +...+val[i]) + val[j-1]*(val[j-2] + val[j-3] + ... " val[i]") + ...+val[i - 1] * val[i]\\ &=val[j] * (cnt[j - 1] - cnt[i - 1]) + cost[i][j - 1] \end{aligned}
cost[i][j]=val[j]∗(val[j−1]+val[j−2]+...+val[i])+val[j−1]∗(val[j−2]+val[j−3]+..."val[i]")+...+val[i−1]∗val[i]=val[j]∗(cnt[j−1]−cnt[i−1])+cost[i][j−1]
如果反推,同理可得另外一個遞推式:
c
o
s
t
[
i
]
[
j
]
=
c
o
s
t
[
i
+
1
]
[
j
]
+
v
a
l
[
i
]
∗
(
c
n
t
[
j
]
−
c
n
t
[
i
]
)
cost[i][j] = cost[i + 1][j] + val[i] * (cnt[j] - cnt[i])
cost[i][j]=cost[i+1][j]+val[i]∗(cnt[j]−cnt[i])
到此,我們的準備工作結束,可以開始推導狀態轉移方程了。
迴歸題目,我們設 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i個數,分成 j j j段能得到的最小序列權值和。
⚠ 閆氏DP分析法--“尋找最後一個狀態不同的點” |
狀態是如何轉移的?我們繼續使用上面的一組示例進行模擬,並尋找最後一個狀態不同的點,並根據這個點發生的狀態轉移推導方程:
d
p
[
2
]
[
1
]
=
c
o
s
t
[
1
]
[
1
]
+
c
o
s
t
[
2
]
[
2
]
d
p
[
3
]
[
1
]
=
m
i
n
(
(
c
o
s
t
[
1
]
[
1
]
+
c
o
s
t
[
2
]
[
3
]
)
,
(
c
o
s
t
[
1
]
[
2
]
+
c
o
s
t
[
3
]
[
3
]
)
)
d
p
[
3
]
[
2
]
=
c
o
s
t
[
1
]
[
1
]
+
c
o
s
t
[
2
]
[
2
]
+
c
o
s
t
[
3
]
[
3
]
d
p
[
4
]
[
1
]
=
m
i
n
(
(
c
o
s
t
[
1
]
[
1
]
+
c
o
s
t
[
2
]
[
4
]
)
,
(
c
o
s
t
[
1
]
[
2
]
+
c
o
s
t
[
3
]
[
4
]
)
,
(
c
o
s
t
[
1
]
[
3
]
+
c
o
s
t
[
4
]
[
4
]
)
)
d
p
[
4
]
[
2
]
=
m
i
n
(
(
c
o
s
t
[
1
]
[
1
]
+
c
o
s
t
[
2
]
[
2
]
+
c
o
s
t
[
3
]
[
4
]
)
,
(
c
o
s
t
[
1
]
[
1
]
+
c
o
s
t
[
2
]
[
3
]
+
c
o
s
t
[
4
]
[
4
]
,
(
c
o
s
t
[
1
]
[
2
]
+
c
o
s
t
[
3
]
[
3
]
+
c
o
s
t
[
4
]
[
4
]
)
)
\begin{aligned} &dp[2][1] = cost[1][1] + cost[2][2]\\ &dp[3][1] = min((cost[1][1] + cost[2][3]),\ (cost[1][2] +cost[3][3])) \\ &dp[3][2] = cost[1][1] + cost[2][2] + cost[3][3]\\ &dp[4][1] = min((cost[1][1] + cost[2][4]),\ (cost[1][2] + cost[3][4]),\ (cost[1][3] + cost[4][4])) \\ &dp[4][2] = min((cost[1][1] + cost[2][2] + cost[3][4]),\ (cost[1][1] + cost[2][3] + cost[4][4],\ (cost[1][2] + cost[3][3] + cost[4][4])) \end{aligned}
dp[2][1]=cost[1][1]+cost[2][2]dp[3][1]=min((cost[1][1]+cost[2][3]),(cost[1][2]+cost[3][3]))dp[3][2]=cost[1][1]+cost[2][2]+cost[3][3]dp[4][1]=min((cost[1][1]+cost[2][4]),(cost[1][2]+cost[3][4]),(cost[1][3]+cost[4][4]))dp[4][2]=min((cost[1][1]+cost[2][2]+cost[3][4]),(cost[1][1]+cost[2][3]+cost[4][4],(cost[1][2]+cost[3][3]+cost[4][4]))
我們將計算式進行類比,不難發現:
d
p
[
4
]
[
2
]
=
m
i
n
(
(
d
p
[
2
]
[
1
]
+
c
o
s
t
[
3
]
[
4
]
)
,
(
d
p
[
3
]
[
1
]
+
c
o
s
t
[
4
]
[
4
]
)
)
dp[4][2] = min((dp[2][1] + cost[3][4]),\ (dp[3][1] + cost[4][4]))
dp[4][2]=min((dp[2][1]+cost[3][4]),(dp[3][1]+cost[4][4]))
繼續列舉幾組例子,觀察最後一個轉移點的變化,可以推匯出狀態轉移方程(沒啥用了,可以不推):
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
k
]
[
j
−
1
]
+
c
o
s
t
[
k
+
1
]
[
i
]
)
,
k
=
1
,
2
,
.
.
.
,
i
−
1
dp[i][j] = min(dp[k][j - 1] + cost[k + 1][i]),\ k = 1,2,...,i-1
dp[i][j]=min(dp[k][j−1]+cost[k+1][i]),k=1,2,...,i−1
至此,動態規劃推導部分結束,按照正常的思路,我們可以敲程式碼了。
此時不妨對時間複雜度進行估計:狀態為 O ( n 2 ) O(n^2) O(n2),轉移時間 O ( n ) O(n) O(n),總共 O ( n 3 ) O(n^3) O(n3),這不是一個優解演算法,甚至會TLE。
當然,我們可以進行降維優化: d p [ i ] = m i n ( d p [ i ] , d p [ k ] + c o s t [ k + 1 ] [ i ] ) dp[i]=min(dp[i],dp[k]+cost[k+1][i]) dp[i]=min(dp[i],dp[k]+cost[k+1][i]),這裡推薦一篇大佬的題解:HDU 3102 Lawrence of Arabia_yechenv,有興趣可以繼續探討這種優化方案,在這種方案中,我們捨棄了對狀態轉移無貢獻的維度。