1. 程式人生 > >【分層最短路】dijkstra講解+複習 /2018 ACM-ICPC南京網路賽 Magical Girl Haze

【分層最短路】dijkstra講解+複習 /2018 ACM-ICPC南京網路賽 Magical Girl Haze

首先,HDU-2544 來回顧一下dij這個東西(連結: 這裡

講解: (來源:坐在馬桶上學演算法) 不斷對邊進行鬆弛

(以下為copy)

演算法的基本思想是:每次找到離源點(上面例子的源點就是 1 號頂點)最近的一個頂點,然後以該頂點為中心進行擴充套件,最終得到源點到其餘所有點的最短路徑。基本步驟如下:

  • 將所有的頂點分為兩部分:已知最短路程的頂點集合 P 和未知最短路徑的頂點集合 Q。最開始,已知最短路徑的頂點集合 P 中只有源點一個頂點。我們這裡用一個 book[ i ]陣列來記錄哪些點在集合 P 中。例如對於某個頂點 i,如果 book[ i ]為 1 則表示這個頂點在集合 P 中,如果 book[ i ]為 0 則表示這個頂點在集合 Q 中。
  • 設定源點 s 到自己的最短路徑為 0 即 dis=0。若存在源點有能直接到達的頂點 i,則把 dis[ i ]設為 e[s][ i ]。同時把所有其它(源點不能直接到達的)頂點的最短路徑為設為 ∞。
  • 在集合 Q 的所有頂點中選擇一個離源點 s 最近的頂點 u(即 dis[u]最小)加入到集合 P。並考察所有以點 u 為起點的邊,對每一條邊進行鬆弛操作。例如存在一條從 u 到 v 的邊,那麼可以通過將邊 u->v 新增到尾部來拓展一條從 s 到 v 的路徑,這條路徑的長度是 dis[u]+e[u][v]。如果這個值比目前已知的 dis[v]的值要小,我們可以用新值來替代當前 dis[v]中的值。
  • 重複第 3 步,如果集合 Q 為空,演算法結束。最終 dis 陣列中的值就是源點到所有頂點的最短路徑。

(copy結束)如上圖, 全部鬆弛完成之後的是這樣的↑

差不多是從起點開始跑,到終點、跑完之後結束。

中間的過程,每次在可選擇的點裡面選一個沒有跑過,並且距離最近的。

一旦被選定,這個點用過了,就要標記,把這個點的出度全部訪問一遍,在這個過程中如果有未曾訪問過的滿足了可更新的條件,就去更新,把這條邊的出度更新完為止。

【對於無向邊,要加個2次,一樣的!】呱呱呱,這就是演算法的美妙之處吧1!!!

更新的是後面的點。每次選定的時候選剩下的最優的,從1-n即可。(所以說上面寫的真好。。。)

然後過程中呢,

每次都存一下,然後最後跑完

【簡單實現一下下】

程式碼:  (18.4.7 ac one)

#include<vector>
#include<iostream>
#include<queue>
using namespace std;
struct node {
	int next;
	int dis;
	bool operator< (const node &p)const {
		return dis > p.dis;
	}
	node(int _next=0,int _dis=0):next(_next),dis(_dis){}
}h[105];
vector<node>e[105];//記錄邊的地方....
priority_queue<node> q;
bool vis[10005];//邊
int dist[10005];
const int INF = 0x3f3f3f3f;
void dij(int x, int n) {
    memset(vis, false, sizeof(vis));
	for (int i = 1; i <= n; i++) dist[i] = INF;
	while (!q.empty()) q.pop();
	dist[x] = 0;//...自己到自己是0  沒錯吧
	q.push(node(x, 0));//吧自己也加進去..沒錯吧
	while (!q.empty()){
		node temp = q.top(); q.pop();
		int u = temp.next;
        if (vis[u]) continue;
		vis[u] = true;
		for (int i = 0; i < e[u].size(); i++){
			int v = e[temp.next][i].next;
			int cost = e[u][i].dis;
			if (!vis[v] && dist[v] > dist[u] + cost){
				dist[v] = dist[u] + cost;
				q.push(node(v, dist[v]));
			}
		}
	}
}
int main() {
	int n, m; while (cin >> n >> m) {
		if (m == 0 && n == 0)break;
		int a1,a2,v;
		for (int i = 0; i < n; i++)e[i].clear();
		for (int i = 0; i < m; i++){
			cin >> a1 >> a2 >> v;
			e[a1].push_back(node(a2, v));//出度和值..
			e[a2].push_back(node(a1, v));
		}
		dij(1, n);
		long long ans = dist[n];
		cout << ans << endl;
	}
	return 0;
}

【寫後反思】dij這個東西,寫的再熟練也不為過吧。啊…… 再熟練也不為過……

q如果沒有滿一樣要清理,如果是迴圈輸入的,把所有遍歷到的東西都初始化一遍吧……

1.邊可以存,但是怎麼存?node裡不是隻有nxt和v嗎?怎麼儲存當前的頂點嗯?

怎麼初始化?不要忽略記錄答案的dist[i] 

q.push( x,0 ) ;   注意這裡是push ,在佇列裡 而不是push_back

node temp = q.top() ;q.pop() ;     比如1可以到2和3,那麼先遍歷1、 1的v是0

【程式碼實現時間到】遍歷1在g[1]裡面的所有條件,其中已經確定住的不去遍歷,沒被確定的能更新就更新

(待續)

注意:vector<node> g 

還有注意排序的時候的序號

2.要對整個過程熟悉,比如為什麼要vis,為什麼要inf,那是因為初始都是不可達,每次搞完一個就確定了,vis在比較的時候已經遍歷了就不用管了

3.優先佇列裡不能寫bool   cmp ,那個是用在sort裡面的

這麼寫:

bool  operator < (node &p) {

return dis>p.dis;}

bool operator< (const node &p)const {

return dis > p.dis;

}

因為預設的優先佇列裡面,是優先順序大的先出來
比如一個序列同樣是12345
優先隊列出來的順序就會變成54321
因為是大的先出來
所以你要把裡面的排序變成54321,它出來的才是12345 所以上面寫的dis>p.dis
這的確是從小到大安排的。。  dis>p.dis

那麼對於這個題... 有兩種方法

for(int i = head[x]; i; i = nxt[i]){//開始鬆弛能到達的點
            int u = to[i];//分兩種情況
            if(dis[x][lev] + val[i] < dis[u][lev]){//在不使任何新的邊為0的情況下的鬆弛,相當於普通dijk
                dis[u][lev] = dis[x][lev] + val[i];
                if(!vis[u][lev]) q.push(Node(dis[u][lev],u,lev));//如果這個點這種狀態還沒當過出發點鬆弛就入隊
            }
            if(lev + 1 <= k && dis[x][lev] < dis[u][lev+1]){//如果是這條邊為0,再進行鬆弛,然後同上判斷是否入隊
                dis[u][lev+1] = dis[x][lev];
                if(!vis[u][lev+1]) q.push(Node(dis[u][lev+1],u,lev+1));
            }
        }

還有是分層讀入

for (int i=1;i<=m;i++)
		{
			scanf("%d%d%d",&u,&v,&c);
			u--;v--;
                        //分層
			for (int j=0;j<=k;j++)
			{
				G[u*(k+1)+j].push_back(node(v*(k+1)+j,c));
				if (j<k)
				G[u*(k+1)+j].push_back(node(v*(k+1)+j+1,0));
			}
		}

都還沒寫

來源: 1   2