炮兵陣地
time limit: 2000ms
memory limit: 65536k
total submissions: 2762
accepted: 776
description
司令部的將軍們打算在n*m的網格地圖上部署他們的炮兵部隊。乙個n*m的地圖由n行m列組成,地圖的每一格可能是山地(用"h" 表示),也可能是平原(用"p"表示),如下圖。在每一格平原地形上最多可以布置一支炮兵部隊(山地上不能夠部署炮兵部隊);一支炮兵部隊在地圖上的攻擊範圍如圖中黑色區域所示:
如果在地圖中的灰色所標識的平原上部署一支炮兵部隊,則圖中的黑色的網格表示它能夠攻擊到的區域:沿橫向左右各兩格,沿縱向上下各兩格。圖上其它白色網格均攻擊不到。從圖上可見炮兵的攻擊範圍不受地形的影響。
現在,將軍們規劃如何部署炮兵部隊,在防止誤傷的前提下(保證任何兩支炮兵部隊之間不能互相攻擊,即任何一支炮兵部隊都不在其他支炮兵部隊的攻擊範圍內),在整個地圖區域內最多能夠擺放多少我軍的炮兵部隊。
input
第一行包含兩個由空格分割開的正整數,分別表示n和m;
接下來的n行,每一行含有連續的m個字元('p'或者'h'),中間沒有空格。按順序表示地圖中每一行的資料。n <= 100;m <= 10。
output
僅一行,包含乙個整數k,表示最多能擺放的炮兵部隊的數量。
sample input
5 4sample outputphpp
pphh
pppp
phpp
phhp
6source
noi 01
分析一 盲目搜尋
初學者一般看到此題估計會無從著手。如果用「萬能」的搜尋演算法,回溯或者列舉所有的狀態來求解的話,那演算法複雜度將是o(2^(m*n))。
又考慮到m<=10,n<=100,這將是個及其恐怖的工作。
大家知道凡是指數級的演算法一般不能作用於較大資料的運算。
分析二 動態規劃
觀察地圖,對於任何一行的炮兵放置都與其上下幾行的放置有關。如果我們逐行的放置炮兵,並且每次都知道前面每行所有放置法的最優解(即最大炮兵數),那麼我們要求放置到當行時某種放置法的最優解,就可以列舉前面與其相容(即不會發生衝突)的所有放置法,從中求得本行的最優解。
那麼就可以把n*m行的最優解裝換成了(n-1)*m行的最優解。此演算法的基礎在於,每行的狀態(炮兵放置情況)只與前幾行的狀態有關。
這滿足最優子問題和無後效性的性質,因此可以使用動態規劃求解。
最優子問題大家都知道。無後效性就是指最優解只與狀態有關,而與到達這種狀態的路徑無關。
此問題的狀態就是指該行的炮兵放置法
動態方程
f[i][j][k] = max,(列舉p的每種狀態)
f[i][j][k]表示第i行狀態為s[j],第i-1行狀態為s[k]的最大炮兵數,且s[j],s[k],s[p]及地形之間互不衝突
演算法複雜度:o(n*s*s*s),n為行數,s為總狀態數
問題如何描述
好了,思路大致都準備好了。但如何描述問題呢?
動態規劃的關鍵就在於如何描述狀態。如何用二進位制串表示狀態的話,那麼在**中表示起來將很複雜,不利於編寫**。
怎麼辦?
狀態壓縮
現在引入最關鍵的感念,狀態壓縮
我們把乙個二進位制串的相應十進位制數稱為該二進位制串的壓縮碼,這就將乙個二進位制串壓縮為乙個簡單的十進位制狀態。
伴隨著這個概念而來的是其相應的位運算,&, |, !,<>等。
相關運算
我們現在就可以用與運算&判斷兩個壓縮狀態間、壓縮狀態與壓縮地圖間是否衝突。
用移位運算》和求餘運算%計算壓縮狀態所包含的炮兵數
困惑?現在似乎大功告成了,但是所寫的**提交執行結果為,time limit exceed,即超時。
為什麼呢?
複雜度解析
看看題目條件吧!time limit: 2000ms memory limit:65536k
我們採用壓縮二進位制方式來表示一行的所有狀態,那麼會有每行會有2^10即1024個狀態。因此在最壞情況下(m=10,n=100,所有地點都是平原),會將掃瞄100*1024*1024*1024(10^11,遠遠超過2s),因此不可取。
o(n*s*s*s)不可取麼?
演算法加速
不!仔細分析,狀態數s真的是2^10麼?
顯然,有些是偽狀態,自身就是個矛盾體。那麼可以提前摒棄這些偽狀態。記過計算,單獨一行(10列)的合法狀態數只有60個!!
求合法狀態的**段
snum = 0; //合法狀態總數
for ( int k = 0; k < (1<>1) ) c[snum] += m%2;
s[snum++] = k; //合法狀態數}
優化考慮到本行最優解f[i][j][k]只與前一行f[i-1][k][p]有關,也就是說每次計算只需要前一行的最優解就可以了。
那只用申請f[2][61][61]的記憶體,就可以實現該演算法,而非f[100][61][61],更非f[100][1025][1025]。
可是,如果用向量f[0]表示當前行的最優解,向量f[1]表示前一行的最優解,那每次迭代計算時豈不是又要交換兩個向量的值?
滾動陣列
借助滾動陣列技術,可以輕鬆實現這個轉換!
引入迭代座標roll,向量f[roll]指向當前行,計算f[roll]時,f[(roll+1)%2]指向前一行,計算結束後,令roll = (roll+1)%2,就可以實現行轉換了。
我們只要初始roll = 0即可,運算結束時,我們不必知道roll的值,但roll必然指向待計算的那行,(roll+1)%2指向最終結果所在行。
執行結果
problem: 1185 user: new_star
memory: 316k time: 235ms
language: g++ result: accepted
小結1.最優子結構和無後效性
2.壓縮狀態的動態規劃
3.位運算
4.滾動陣列
poj 1185 炮兵陣地
題目鏈結 題意 在n m的網格地圖上部署炮兵部隊。地圖的每一格可能是山地 用 h 表示 也可能是平原 用 p 表示 如下圖。在每一格平原地形上最多可以布置一支炮兵部隊 山地上不能夠部署炮兵部隊 一支炮兵部隊在地圖上的攻擊範圍如圖中黑色區域所示 如果在地圖中的灰色所標識的平原上部署一支炮兵部隊,則圖中...
POJ 1185 炮兵陣地
include include include include include include include include include include include include include include define sz v int v size define rep i,n ...
POJ 1185 炮兵陣地
狀態壓縮專題第一題,自己想了很久,最終還是以別人的 為模板寫的。dp共三維,一維是行數,一維是前一行狀態,一維是前第二行狀態。ps 直接開三維太大,用s陣列記錄下所有可能出現的情況,大大減少時間和空間。include include include includeusing namespace st...