Dijkstra演算法:有向圖G=(V,E)
1、把源點v0放入到集合S中,計數器c=0。每個結點一個結構資料d(a,b), a表示到v0的最短路徑長,b表示a的前趨節點。初始化v0為d(0,-)並且除v0外的所有節點為d(INF,-)。這裡面INF表示無窮大。
2、通過遍曆集合S中所有節點v,來更新非集合S中所有節點u.
更新條件為 d[u].first = min{du[u].first, d[v].first + wt(v,u)},其中d[u].first表示u節點資料的每一個元素,即u節點到源點最短路徑長。wt(v,u)表示節點v到節點u的路徑長。除了更新最短路徑長,而且更新前趨節點。
此時,所有非集合S中節點資料更新完畢。如果
a、非集合S中所有節點資料d為(INF,-),此時的圖為所求,表示這些非集合S中節點由源點不可達。
b、否則,從這個節點中選擇d.first最少的節點加入到集合S中,計數器加一
3、當計數器c=n-1時,此時圖為所求,並且所有節點可達。
最佳化:第2步中更新所有非集合S中節點時,不需要遍曆所有集合S中節點,只需要判斷上一次迭代新加入的節點即可。
題意:探險家如何用最少的金幣獲得酋長所要的物品。每個節點附有等級、金幣數的值。如果A物品可以用B物品和C個金幣代替,那麼由A到B的邊權值為C。
分析:每個物品為一個節點。酋長所要物品為源節點。求源節點到其它節點的最短路徑。結果是到所有節點最短路徑加上物品價值和的最小值。說幾點需要注意的問題:
1、等級的處理:題中規定等級超過m的物品不可以交換,包括間接交換。比如a、b、c等級為1、2、3,如果等級限制為1的話,不可以通過a換b,b再換c這種方式,因為a與c的等級超過1。所以如果單次dijkstra求最短路徑時,我們很難確定是否加入非集合S的的節點,因為不清楚當前節點是否滿足等級要求,或者是否對未來要加入節點等級造成影響。所以目前採用的是枚舉範圍的方法,有一個事情我們是確定的,即初始節點的等級一定是在等級範圍裡的。所以我們枚舉(lev[s]-m, lev[s])到(lev[s],lev[s]+m)等級範圍。在處理最短路徑新加入節點時要求節點等級大於等於最低等級,小於等於最高等級。
2、有向圖問題:A物品可以用B物品和C個金幣代替,表示A到B的邊權為C,但是並不說明B到A也有一條權為C的邊,注意!
3、初始化問題:既然要多次運行dijkstra,那麼最小路徑長度一定要初始化。我前幾次WA就是錯在這裡,因為每次dijstra資料具有相似性,所以好多資料都檢查不出來。
4、表示無窮大的INF=(1>>30)-1足夠,鄰接矩陣用int也足夠,並不像DISCUSS中有人提到的要開INF=(1>>31)-1.
源碼如下:
#include <iostream><br />#include <cstring><br />using namespace std;</p><p>const int N = 128;<br />const int INF = (1<<30)-1;</p><p>int adj[N][N];<br />int lev[N];<br />int val[N];<br />int V[N];</p><p>int m;//等級差距<br />int littleLev, bigLev;//本次dijkstra的最低與最高等級</p><p>/*s為源點,n為頂點數*/<br />int dijkstra(int s, int n){<br /> bool is[N];<br /> int pre = s;</p><p> memset(is,false,sizeof(is));<br /> for(int i=0;i<n;i++)<br /> V[i]=INF;;<br /> is[s]=true, V[s]=0;<br /> for(int j=0;j<n-1;j++){<br /> int min=INF,id;<br /> for(int i=0;i<n;i++) if(!is[i])<br /> if(lev[i]>=littleLev && lev[i]<=bigLev)//去掉這個等級約束就是標準的單源最短路徑<br /> {<br /> if(V[i] > V[pre] + adj[pre][i])<br /> V[i] = V[pre] + adj[pre][i];<br /> if(min > V[i])<br /> min = V[id=i];<br /> }<br /> if(min!=INF)//加入新點<br /> pre=id, is[id]=true;<br /> else//表示有些點不可達<br /> break;<br /> }<br /> int min = INF;<br /> for(int i=0;i<n;i++) if(V[i]!=INF)//求最短路時需要加上最下層物品價格<br /> if(min > V[i] + val[i])<br /> min=V[i] + val[i];<br /> return min;<br />}</p><p>int main()<br />{<br /> int n;</p><p> cin >> m >> n;<br /> for(int i=0;i<n;i++)<br /> for(int j=0;j<n;j++)<br /> adj[i][j]=(i==j) ? 0 : INF;//除對角邊其它應該初始為無窮大<br /> for(int i=0;i<n;i++){<br /> int c, dis, id;<br /> cin >> val[i] >> lev[i] >> c;<br /> for(int j=0;j<c;j++){<br /> cin >> id >> dis;<br /> adj[i][id-1]=dis;//有向圖這點注意<br /> }<br /> }<br /> int min=INF,ans;<br /> for(int i=lev[0]-m;i<=lev[0];i++){//枚舉所有等級範圍<br /> littleLev = i;<br /> bigLev = i+m;<br /> ans=dijkstra(0,n);<br /> if(min > ans)<br /> min=ans;<br /> }<br /> cout << min << endl;</p><p> return 0;<br />}<br />