《原神攻略》淥華池之影拍照方法 淥華池之影拍照步驟
最短路
記號約定
\(n\)是點數,\(m\)是邊數。
\(s\)是源點。
\(D\left(u\right)\)是從\(s\)點到\(u\)點的實際最短路,\(D\left(u,v\right)\)是從\(u\)點到\(v\)點的實際最短路。
\(dis\left(u\right)\)是從\(s\)點到\(u\)點的即時最短路,\(dis\left(u,v\right)\)是從\(u\)點到\(v\)點的即時最短路。
\(w\left(u,v\right)\)是從\(u\)點到\(v\)點的邊權值。
說明
由於全源最短路中的\(Johnson\)演算法需要\(SPFA\)與\(Dijkstra\)
若一個圖上存在負環,即負權邊連成的環,就是負環。在有負環的圖中,我們可以不斷的繞著負環,所以最短路為\(-\infty\)。
單源最短路
單源最短路,是從一個源點到其餘點的最短路。
\(SPFA\)
即\(Shortest\ Path\ Faster\ Algorithm\) (更快的最短路)
鬆弛操作
對於節點\(u\),對於所有與\(u\)點相連的\(v\)節點,使\(dis\left(v\right)=\min\left(dis\left(u\right),dis\left(u\right)+w\left(u,v\right)\right)\)
這麼做的目的是顯然的,用\(s\to u\)
過程
我們發現只有被進行過鬆弛操作的節點才有用來鬆弛的價值,這是顯然的,如果沒有經過鬆弛,就沒有可能更新最短路。
所以我們可以使用一個佇列,將被鬆弛過的節點放入佇列中,每次從隊頭取出節點進行鬆弛,最後佇列為空,就表示最短路演算法的結束。
在實際實現中,要判斷是否存在負環。實際上,我們可以證明一個節點最多被鬆弛\(n\)次,所以在實現中,我們記錄每個節點鬆弛的次數,就能判斷出負權圖。
性質
時間複雜度\(\Omega\left(km\right)\)或\(O\left(nm\right)\),\(k\)為常數,一般為二。
由於程式碼中沒有對最短路的不降要求,所以可以用於有負權邊的圖。
\(Code:\)
void SPFA(int s){
memset(dis,0x7f,sizeof(dis));
memset(vis,false,sizeof(vis));
dis[s]=0;
vis[s]=true;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]>dis[u]+val[i]){
dis[v]=dis[u]+val[i];
if(!vis[x]){
vis[v]=true;
q.push(v);
}
}
}
}
}
\(Dijkstra\)
\(Dijkstra\)只能用在非負權邊的圖上。
過程
將原圖中的點分為兩個集合,\(T\)所有未確定最短路的節點集合,\(S\)為已確定的集合,顯然\(D\left(s\right)=dis\left(s\right)=0\)。
從源點開始,反覆重複下列操作:
\(1.\)從\(T\)集合中取出最短路最小的節點\(u\),放入集合\(S\)。
\(2.\)用\(u\)去鬆弛所有出邊\(\left(u,v\right)\)。
直到\(T=\emptyset\),演算法結束。
正確性證明
\(T\)集合為未確定最短路的集合,移出\(T\)集合就意味著\(u\)點的最短路確定,即\(dis\left(u\right)=D\left(u\right)\),證明的關鍵,就是證明只用一次鬆弛就能將最短路確定下來。
我們設\(k\)為當前操作次數。
命題:\(\forall k\in N_{+},D\left(u\right)=dis\left(u\right)\)
證明:
當\(k=1\)時,\(S=\left\{s\right\}\),\(D\left(u\right)=dis\left(u\right)=0\)
設當\(k=l\)時成立,當\(k=l+1\)時:
記\(v\)點是\(l+1\)步時選擇的節點,我們設\(v\)點是由\(u\)點鬆弛的,則\(u\in S\),即\(D\left(u\right)=dis\left(u\right)\)。
若\(v\)點的最短路徑不是\(s\to u\to v\),則在\(v\)點的最短路徑上有一個或多個節點\(\in T\),即存在一條路徑\(s\to x\to y\to v\),其中\(y\)是路徑上第一個不屬於\(T\)集合的節點,\(x\)是\(y\)的前驅節點。
因為邊權非負,有\(D\left(y\right)\le dis\left(y\right)\le D\left(v\right)\le dis\left(v\right)\)
由於當\(v\)節點被取出時,\(y\)節點沒有被取出,所以\(dis\left(v\right)\le dis\left(y\right)\)
\(\therefore D\left(v\right)=dis\left(v\right)\)
\(\therefore s\to u\to v\)是\(v\)點的最短路
由數學歸納法得,命題成立。
性質
根據實現方式的不同,有不同的時間複雜度。
優先佇列:\(O\left(m\log m\right)\)
\(Code:\)
struct node{
int num,dist;
bool operator <(const node &a)const{return dist>a.dist;}
node(int a,int b){
num=a,dist=b;
}
};
priority_queue<node>q;
void Dijkstra(int s){
memset(vis,false,sizeof(vis));
memset(dis,0x7f,sizeof(dis));
dis[s]=0;
q.push(node(s,0));
while(!q.empty()){
int point=q.top().num;
q.pop();
if(vis[point])continue;
vis[point]=true;
for(int i=head[point];i;i=nxt[i]){
int son=to[i];
if(dis[son]>dis[point]+1){
dis[son]=dis[point]+1;
q.push(node(son,dis[son]));
}
}
}
}
全源最短路
即任意兩點之間的最短路。
\(Floyed\)
原理
略。
性質
無負環圖。
時間複雜度\(O\left(n^{3}\right)\),空間複雜度\(O\left(n^{2}\right)\)
\(Code:\)
for (k = 1; k <= n; k++) {
for (x = 1; x <= n; x++) {
for (y = 1; y <= n; y++) {
f[x][y] = min(f[x][y], f[x][k] + f[k][y]);
}
}
}
\(Johnson\)
(挖坑)