1. 程式人生 > 遊戲攻略 >《原神攻略》淥華池之影拍照方法 淥華池之影拍照步驟

《原神攻略》淥華池之影拍照方法 淥華池之影拍照步驟

最短路

記號約定

\(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\)

的最短路去更新\(s\to v\)的最短路。

過程

我們發現只有被進行過鬆弛操作的節點才有用來鬆弛的價值,這是顯然的,如果沒有經過鬆弛,就沒有可能更新最短路。

所以我們可以使用一個佇列,將被鬆弛過的節點放入佇列中,每次從隊頭取出節點進行鬆弛,最後佇列為空,就表示最短路演算法的結束。

在實際實現中,要判斷是否存在負環。實際上,我們可以證明一個節點最多被鬆弛\(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\)

(挖坑)