圖論(2):最短路

最短路

  • 一、模板
    • 1. Floyd
    • 2. 01BFS
    • 3. SPFA
    • 4. Dijkstra(弱化版)
    • 5. Dijkstra(優化版)
  • 二、例題
    • 1. Floyd
      • 1.1 傳送門
      • 1.2 無向圖最小環
      • 1.3 災后重建
      • 1.4 飛豬
    • 2. 01BFS
      • 2.1 Kathiresan
      • 2.2 障礙路線
      • 2.3 奇妙的棋盤
    • 3. SPFA
      • 3.1 奶牛派對
      • 3.2 營救
      • 3.3 航空路線
      • 3.4 牛奶管道
    • 4. Dijkstra
      • 4.1 路徑統計
      • 4.2 平面上的點
      • 4.3 邀請函

一、模板

1. Floyd

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e2+8;
const int INF=0x3f3f3f3f;
int n,m,dis[MAXN][MAXN];
int main(){cin>>n>>m;memset(dis,INF,sizeof(dis));for(int u=1;u<=n;u++)dis[u][u]=0;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,dis[u][v]=min(dis[u][v],w);for(int k=1;k<=n;k++)for(int u=1;u<=n;u++)for(int v=1;v<=n;v++)dis[u][v]=min(dis[u][v],dis[u][k]+dis[k][v]);for(int u=1;u<=n;u++)for(int v=1;v<=n;v++)cout<<(dis[u][v]==INF?-1:dis[u][v])<<" \n"[v==n];return 0;
}

2. 01BFS

原題:求 KKK 的所有非 000 倍數中十進制表示下數碼和的最小值

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+8;
const int INF=0x3f3f3f3f;
int k,dis[MAXN];//除以k余數為x的數中最小的數碼和
int bfs(){deque<int>dque;dis[1]=1,dque.push_back(1);while(!dque.empty()){int x=dque.front(),nx;dque.pop_front();if(x==0)return dis[x];//代價為0的操作,放在隊首nx=x*10%k;//x后面直接加0,十進制數碼和不變,放在隊首if(dis[nx]>dis[x])dis[nx]=dis[x],dque.push_front(nx);//代價為1的操作,放在隊尾nx=(x+1)%k;//x+1和x的數碼和差1,放在隊尾if(dis[nx]>dis[x]+1)dis[nx]=dis[x]+1,dque.push_back(nx);}
}
int main(){cin>>k;memset(dis,INF,sizeof(dis));cout<<bfs();return 0;
}

3. SPFA

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
int n,m,s,dis[MAXN];
bool in_que[MAXN];
vector<Edge>adj[MAXN];
void spfa(int s){queue<int>que;dis[s]=0,in_que[s]=true,que.push(s);while(!que.empty()){int u=que.front();in_que[u]=false,que.pop();for(auto[v,w]:adj[u])if(dis[v]>dis[u]+w){dis[v]=dis[u]+w;if(!in_que[v])in_que[v]=true,que.push(v);}}
}
int main(){cin>>n>>m>>s;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,adj[u].push_back({v,w});memset(dis,INF,sizeof(dis));spfa(s);for(int i=1;i<=n;i++)cout<<(dis[i]==INF?INT32_MAX:dis[i])<<" ";return 0;
}

4. Dijkstra(弱化版)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
bool vis[MAXN];
int n,m,s,t,dis[MAXN];
vector<Edge>adj[MAXN];
void dijkstra(int s){dis[s]=0;for(int i=1;i<=n;i++){int u=0;for(int j=1;j<=n;j++)if(!vis[j]&&dis[j]<dis[u])u=j;if(!u)return;vis[u]=true;for(auto[v,w]:adj[u])if(!vis[v]&&dis[v]>dis[u]+w)dis[v]=dis[u]+w;}
}
int main(){cin>>n>>m>>s>>t;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,adj[u].push_back({v,w}),adj[v].push_back({u,w});memset(dis,INF,sizeof(dis));dijkstra(s);cout<<dis[t];return 0;
}

5. Dijkstra(優化版)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
bool vis[MAXN];
int n,m,s,t,dis[MAXN];
vector<Edge>adj[MAXN];
void dijkstra(int s){//兩種方法均可,推薦沒有注釋的代碼priority_queue<pair<int,int> >pq;//{-dis[u],u}dis[s]=0,pq.push({0,s});while(!pq.empty()){auto u=pq.top().second;pq.pop();if(vis[u])continue;vis[u]=true;for(auto[v,w]:adj[u])if(dis[v]>dis[u]+w)dis[v]=dis[u]+w,pq.push({-dis[v],v});}// priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >pq;// dis[s]=0,pq.push({0,s});// while(!pq.empty()){//     auto[d,u]=pq.top();//     pq.pop();//     if(d>dis[u])continue;//     for(auto[v,w]:adj[u])//         if(dis[v]>dis[u]+w) {//             dis[v]=dis[u]+w;//             pq.push({dis[v],v});//         }// }
}
int main(){cin>>n>>m>>s;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,adj[u].push_back({v,w});memset(dis,INF,sizeof(dis));dijkstra(s);for(int i=1;i<=n;i++)cout<<(dis[i]==INF?INT32_MAX:dis[i])<<" ";return 0;
}

二、例題

1. Floyd

1.1 傳送門

香蕉大學里有 nnn 棟教學樓,有 mmm 條雙向通行道路連接這些教學樓,不存在重邊和自環。每條道路都有一定的長度,而且所有教學樓之間都可以直接或者間接的通過道路到達。我們可以很容易的求出這些教學樓之間的最短路。
為了使交通更為順暢,校方決定在兩個教學樓里增設一對傳送門。傳送門可以將這對教學樓的距離直接縮短為 000。利用傳送門,某些教學樓之間的最短路的距離就變短了。
由于預算有限,學校里只能安裝一對傳送門。但是校長希望盡可能方便學生,使任意兩點之間的最短路長度的總和最小。當然啦,從 xxx 教學樓到 yyy 教學樓的長度和從 yyy 教學樓到 xxx 教學樓的長度只需要統計一次就可以了。


標準的一道暴力枚舉題。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e2+8;
const int INF=0x3f3f3f3f;
int n,m,dis[MAXN][MAXN];
int main(){cin>>n>>m;memset(dis,INF,sizeof(dis));for(int u=1;u<=n;u++)dis[u][u]=0;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,dis[u][v]=dis[v][u]=min(dis[u][v],w);for(int k=1;k<=n;k++)for(int u=1;u<=n;u++)for(int v=1;v<=n;v++)dis[u][v]=min(dis[u][v],dis[u][k]+dis[k][v]);int ans=INF;for(int i=1;i<=n;i++)//枚舉傳送門端點Afor(int j=i+1;j<=n;j++){//枚舉傳送門端點Bint sum=0;//存儲安裝傳送門后所有點對的最短路總和for(int u=1;u<=n;u++)//枚舉所有起點計算最短路總和for(int v=u+1;v<=n;v++)//枚舉所有終點計算最短路總和   sum+=min({//因為i和j間有傳送門,視其距離為0dis[u][v],dis[u][i]+dis[j][v],dis[u][j]+dis[i][v]});ans=min(ans,sum);//每次更新答案到最小值}cout<<ans;return 0;
}

1.2 無向圖最小環

給定一個 nnn 個點 mmm 條帶正權邊且無重邊的無向圖,請輸出圖中簡單環的最小權值和。特別地,如果圖中沒有簡單環則輸出 "No Simple Cycle"。本題中的簡單環是指,不重復經過同一個點,且不重復經過同一條邊的環。


比較經典的一道松弛類型的題目。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e2+8;
const int INF=0x3f3f3f3f;
int t,dis[MAXN][MAXN],tmp[MAXN][MAXN];
int main(){cin>>t;while(t--){int n,m;cin>>n>>m;memset(dis,INF,sizeof(dis));memset(tmp,INF,sizeof(tmp));for(int u=1;u<=n;u++)dis[u][u]=tmp[u][u]=0;for(int j=1,u,v,w;j<=m;j++){cin>>u>>v>>w;dis[u][v]=dis[v][u]=min(dis[u][v],w);tmp[u][v]=tmp[v][u]=min(tmp[u][v],w);}int ans=INF;//最小環的權值和for(int k=1;k<=n;k++){//尋找所有i<k和j<k的通過k的形如i-j-k-i的環for(int i=1;i<k;i++)for(int j=i+1;j<k;j++)if(dis[i][j]!=INF&&tmp[j][k]!=INF&&tmp[k][i]!=INF)ans=min(ans,dis[i][j]+tmp[j][k]+tmp[k][i]);//使用tmp而非dis,防止重復頂點的出現for(int i=1;i<=n;i++)//用Floyd松弛for(int j=1;j<=n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);}if(ans==INF)cout<<"No Simple Cycle\n";else cout<<ans<<"\n";}return 0;
}

1.3 災后重建

題目傳送門


由于題目保證每次輸入都是不下降的,考慮尋找已經重建好的村莊 kkk 作為中轉站,以尋找兩村莊之間的最短路進行更新和輸出。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e2+8;
const int INF=0x3f3f3f3f;
int n,m,q,tms[MAXN],dis[MAXN][MAXN];
int main(){cin>>n>>m;memset(dis,INF,sizeof(dis));for(int u=0;u<n;u++)dis[u][u]=0;for(int i=0;i<n;i++)cin>>tms[i];for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,dis[u][v]=dis[v][u]=min(dis[u][v],w);cin>>q;for(int i=1,x,y,t,k=0;i<=q;i++){cin>>x>>y>>t;while(k<n&&tms[k]<=t){for(int u=0;u<n;u++)for(int v=0;v<n;v++)dis[u][v]=min(dis[u][v],dis[u][k]+dis[k][v]);k++;}if(tms[x]<=t&&tms[y]<=t&&dis[x][y]<INF)cout<<dis[x][y]<<"\n";else cout<<"-1\n";}return 0;
}

1.4 飛豬

給定一個 nnn 個點 mmm 條邊的邊帶權簡單連通無向圖,在 000 時刻你在點 111 上。
假設當前是 ttt 時刻,你在點 vvv 上,你可以選擇兩種操作:

  • 仍停留在點 vvv 上,操作后到 t+1t+1t+1 時刻。
  • 選擇一條邊 (a,b,w)(a,b,w)(a,b,w) 滿足 a=va=va=vb=vb=vb=v,則你到這條邊連接的另一個點上,操作后到 t+wt+wt+w 時刻。

kkk 條信息,每一條信息形如 (ti,vi)(t_i,v_i)(ti?,vi?) 表示在 tit_iti? 時刻,點 viv_ivi? 上會出現一只飛豬,其編號為 iii,若該時刻你在 viv_ivi? 上,則你捕獲到了 iii 號飛豬。
現在你需要求出你能捕獲到的飛豬數量的最大值。


看到中間分的兩種情況可以立刻想到動態規劃的做法,再結合要從 aaa 到達 bbb 想到最短路。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e2+8;
const int MAXK=5e3+8;
const int INF=0x3f3f3f3f;
int n,m,k,dis[MAXN][MAXN],dp[MAXK];//dp[i]表示捕獲第i只飛豬時的最大飛豬數量
struct Pig{int t,idx;bool operator<(const Pig&rhs)const{return t<rhs.t;}
}pigs[MAXK];
int main(){cin>>n>>m>>k;memset(dis,INF,sizeof(dis));for(int u=1;u<=n;u++)dis[u][u]=0;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,dis[u][v]=dis[v][u]=min(dis[u][v],w);for(int k=1;k<=n;k++)//Floydfor(int u=1;u<=n;u++)for(int v=1;v<=n;v++)dis[u][v]=min(dis[u][v],dis[u][k]+dis[k][v]);for(int i=1;i<=k;i++)cin>>pigs[i].t>>pigs[i].idx;sort(pigs+1,pigs+k+1);memset(dp,-INF,sizeof(dp));pigs[0].idx=1,dp[0]=0;//0時刻在節點1(初始位置)捕獲0只飛豬for(int i=1;i<=k;i++)//遍歷每只飛豬ifor(int j=0;j<i;j++)//嘗試從之前的飛豬j轉移過來if(pigs[j].t+dis[pigs[j].idx][pigs[i].idx]<=pigs[i].t)//從j的時間和位置,能否在i出現前到達i的位置dp[i]=max(dp[i],dp[j]+1);//若可達,則更新dp[i]為捕獲j后再捕獲i的數量int ans=0;for(int i=1;i<=k;i++)ans=max(ans,dp[i]);cout<<ans;return 0;
}

2. 01BFS

2.1 Kathiresan

Kathiresan 被關在一個高度戒備的矩形監獄中的單元格 (0,0)(0, 0)(0,0)。他必須到達 (R?1,C?1)(R?1, C?1)(R?1,C?1) 處的大門才能逃出監獄。Kathiresan 可以從任何單元格移動到其四個相鄰單元格之一,如果 Kathiresan 當前位于 (x1,y1)(x_1,y_1)(x1?,y1?),那么他可以移動到 (x2,y2)(x_2,y_2)(x2?,y2?) 當且僅當它們的曼哈距離為 1110≤x2<R0≤x_2<R0x2?<R0≤y2<C0≤y2<C0y2<C。Kathiresan 設法獲得了監獄的地圖。如果 mapx1,y1=mapx2,y2\text{map}_{x_1,y_1}=\text{map}_{x_2,y_2}mapx1?,y1??=mapx2?,y2??,則 Kathiresan 可以從 (x1,y1)(x_1,y_1)(x1?,y1?) 移動到 (x2,y2)(x_2,y_2)(x2?,y2?) 而不打暈任何守衛。如果 mapx1,y1≠mapx2,y2\text{map}_{x_1,y_1} \ne \text{map}_{x_2,y_2}mapx1?,y1??=mapx2?,y2??,則 Kathiresan 可以通過打暈一名守衛從 (x1,y1)(x_1,y_1)(x1?,y1?) 移動到 (x2,y2)(x_2,y_2)(x2?,y2?)。給定監獄的地圖,找出為了逃出監獄,Kathiresan必須打暈的最少守衛數量。


顯然,代價為 000 的操作是直接移動,代價為 111 的操作是打倒一名守衛然后移動。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e3+8;
const int INF=0x3f3f3f3f;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
int t,n,m,dis[MAXN][MAXN],grid[MAXN][MAXN];//dis:到每個單元格的最小代價
int bfs(int sx,int sy){deque<pair<int,int> >dque;dis[sx][sy]=0,dque.push_back({sx,sy});while(!dque.empty()){auto[x,y]=dque.front();dque.pop_front();if(x==n&&y==m)return dis[n][m];for(int i=0;i<4;i++){int nx=x+dx[i],ny=y+dy[i],w=grid[nx][ny]!=grid[x][y];if(grid[nx][ny]==-1||dis[nx][ny]<=dis[x][y]+w)continue;//如果超出地圖或者該格有當前最優解dis[nx][ny]=dis[x][y]+w;//保證隊列始終按距離排序,先處理距離近的單元格w?dque.push_back({nx,ny}):dque.push_front({nx,ny});}}
}
int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>t;while(t--){memset(dis,INF,sizeof(dis));memset(grid,-1,sizeof(grid));cin>>n>>m;char ch;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>ch,grid[i][j]=ch;cout<<bfs(1,1)<<"\n";}return 0;
}

2.2 障礙路線

考慮一個 N×NN \times NN×N1≤N≤1001 \le N \le 1001N100)的由一個個方格組成的正方形牧場。有些方格是奶牛們不能踏上的,它們被標記為了 'x'。例如下圖:

. . B x .
. x x A .
. . . x .
. x . . .
. . x . .

貝茜發現自己恰好在點 AAA 處,她想去 BBB 處的舔鹽塊。但是奶牛是一種緩慢而且笨拙的動物,十分討厭轉彎。盡管如此,必要的時候她們還是會轉彎的。對于一個給定的牧場,請你計算從 AAABBB 最少的轉彎次數。開始的時候,貝茜可以面對任意一個方向,并且貝茜知道她一定可以到達 BBB 處。


遇到轉彎類題目一定要把 dis 數組多開一個方向維度。可以發現,代價為 000 的操作是不轉彎,代價為 111 的操作是轉彎。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e2+8;
const int INF=0x3f3f3f3f;
const int dx[]={-1,0,1,0};
const int dy[]={0,1,0,-1};
int n,sx,sy,ex,ey,grid[MAXN][MAXN],dis[MAXN][MAXN][5];
int bfs(int sx,int sy){deque<tuple<int,int,int> >dque;for(int i=0;i<4;i++)dis[sx][sy][i]=0,dque.push_back({sx,sy,i});while(!dque.empty()){auto[x,y,dir]=dque.front();dque.pop_front();if(x==ex&&y==ey)return dis[x][y][dir];for(int i=0;i<4;i++){int nx=x+dx[i],ny=y+dy[i],w=i!=dir;if(grid[nx][ny]<0||dis[nx][ny][i]<=dis[x][y][dir]+w)continue;dis[nx][ny][i]=dis[x][y][dir]+w;w?dque.push_back({nx,ny,i}):dque.push_front({nx,ny,i});}}
}
int main(){memset(dis,INF,sizeof(dis));memset(grid,-1,sizeof(grid));cin>>n;char ch;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){cin>>ch;if(ch=='.')grid[i][j]=0;if(ch=='A')sx=i,sy=j,grid[i][j]=0;if(ch=='B')ex=i,ey=j,grid[i][j]=0;}cout<<bfs(sx,sy);return 0;
}

2.3 奇妙的棋盤

小猴在玩一個神奇的游戲,游戲中給出了一個 n×mn\times mn×m 的棋盤,棋盤中的格子有的黑,有的白。我們每次可以選擇任意一個格子進行操作,然后這個格子和所有與它顏色相同的相鄰的同色連通塊中的所有格子的顏色全部取反。小猴想知道至少需要多少次操作可以使所有格子變成白色。


代價為 000 的操作顯然是相鄰格子顏色相同,而代價為 111 則是相鄰格子顏色不同。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e2+8;
const int INF=0x3f3f3f3f;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
int n,m,col[MAXN][MAXN],dis[MAXN][MAXN];
//col:存儲格子顏色 1黑0白
//dis:到達(x,y)所需的最少操作次數
int bfs(int sx,int sy){deque<pair<int,int> >dque;dis[sx][sy]=0,dque.push_back({sx,sy});int ret=0;//記錄最終需要的最少操作次數while(!dque.empty()){auto[x,y]=dque.front();dque.pop_front();//更新處理到該位置時累計需要的操作次數ret=max(ret,dis[x][y]+col[x][y]);//(x,y)是黑色說明要翻轉一次,否則不用翻轉for(int i=0;i<4;i++){int nx=x+dx[i],ny=y+dy[i],w=col[nx][ny]!=col[x][y];if(col[x][y]<0||dis[nx][ny]<=dis[x][y]+w)continue;dis[nx][ny]=dis[x][y]+w;//更新到達新位置的操作次數w?dque.push_back({nx,ny}):dque.push_front({nx,ny});}}return ret;
}
int main(){cin>>n>>m;memset(col,-1,sizeof(col));char ch;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>ch,col[i][j]=ch=='B';int ans=INF;for(int x=1;x<=n;x++)for(int y=1;y<=m;y++)if(col[x][y]!=col[x-1][y]&&col[x][y]!=col[x][y-1])memset(dis,INF,sizeof(dis)),ans=min(ans,bfs(x,y));cout<<ans;return 0;
}

3. SPFA

3.1 奶牛派對

寒假到了,nnn 頭牛都要去參加一場在編號為 xxx 的牛的農場舉行的派對,農場之間有 mmm 條有向路,每條路都有一定的長度。每頭牛參加完派對后都必須回家,無論是去參加派對還是回家,每頭牛都會選擇最短路徑,求這 nnn 頭牛的最短路徑(一個來回)中最長的一條路徑長度。


按照樸素寫法,我們可以先計算出 n?1n-1n?1 個結點到 xxx 的最短距離,然后再計算出 xxxn?1n-1n?1 個結點的最短距離。時間復雜度 O(nkm+km)O(nkm+km)O(nkm+km),顯然會超時。

考慮反向圖優化。由于一個結點 aaabbb 的反向圖搜索最短路就相當于 bbbaaa 的正向圖搜索最短路。這樣在主函數中,可以只搜索兩次,都以派對作為起點進行搜索。時間復雜度 O(2km)O(2km)O(2km),不會超時。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e3+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
bool in_que[MAXN];
vector<Edge>adj[2][MAXN];
int n,m,x,dis[MAXN],ans[MAXN];
void spfa(int s,int rev){queue<int>que;dis[s]=0,in_que[s]=true,que.push(s);while(!que.empty()){int u=que.front();in_que[u]=false,que.pop();for(auto[v,w]:adj[rev][u])if(dis[v]>dis[u]+w){dis[v]=dis[u]+w;if(!in_que[v])in_que[v]=true,que.push(v);}}
}
int main(){cin>>n>>m>>x;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,adj[0][u].push_back({v,w}),adj[1][v].push_back({u,w});memset(dis,INF,sizeof(dis));spfa(x,0);//第一次計算所有奶牛從派對回家的最短路for(int i=1;i<=n;i++)ans[i]=dis[i];memset(dis,INF,sizeof(dis));spfa(x,1);//第二次計算所有奶牛從家去派對的最短路int mx=0;for(int i=1;i<=n;i++)ans[i]+=dis[i],mx=max(mx,ans[i]);cout<<mx;return 0;
}

3.2 營救

媽媽下班回家,街坊鄰居說小明被一群陌生人強行押上了警車!媽媽豐富的經驗告訴她小明被帶到了 ttt 區,而自己在 sss 區。該市有 mmm 條大道連接 nnn 個區,一條大道將兩個區相連接,每個大道有一個擁擠度。小明的媽媽雖然很著急,但是不愿意擁擠的人潮沖亂了她優雅的步伐。所以請你幫她規劃一條從 sssttt 的路線,使得經過道路的擁擠度最大值最小。


SPFA 的變種,賊水。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
int n,m,s,t,dis[MAXN];
bool in_que[MAXN];
vector<Edge>adj[MAXN];
void spfa(int s){queue<int>que;dis[s]=0,in_que[s]=true,que.push(s);while(!que.empty()){int u=que.front();in_que[u]=false,que.pop();for(auto[v,w]:adj[u])if(dis[v]>max(dis[u],w)){dis[v]=max(dis[u],w);if(!in_que[v])in_que[v]=true,que.push(v);}}
}
int main(){cin>>n>>m>>s>>t;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,adj[u].push_back({v,w}),adj[v].push_back({u,w});memset(dis,INF,sizeof(dis));spfa(s);cout<<dis[t];return 0;
}

3.3 航空路線

Bessie 對她農場那寒冷的天氣感到厭煩,于是她準備坐飛機到一個更溫暖的地方度假。不幸的是,她發現只有一個航空公司:Air Bovinia,愿意賣票給牛……并且這些票的結構有些復雜。Air Bovinia 擁有 NNN1≤N≤10001≤N≤10001N1000)條航線,每條航線都經過兩個及以上的城市。舉個例子:某航線的一次航班可以從城市 111 出發,然后飛往城市 555,再飛到城市 222,最后飛到城市 888。任何城市都不會在同一條航線上出現多次。如果 Bessie 選擇了一條航線,那么她可以從航線上的任意一個城市上飛機,然后在途中任意一個城市下飛機。她不必從航線的起點上飛機,再從航線的終點下飛機。每條航線都有一個確定的花費,只要它搭乘了這個航班,她就必須支付這個航班的全額花費,不論她途經了幾個城市。如果 Bessie 多次搭乘了某個航班,那么每次搭乘 Bessie 都必須支付航班的花費。Bessie 想要找到從她農場所在的城市(城市 AAA)到她目的地所在城市(城市 BBB)最便宜的路線。請你告訴她最少要花多少錢,并告訴她在此基礎上她最少經過多少個城市(坐飛機經過也算)。


抓住最后要求的重點:(1)要花多少錢;(2)要最少經過多少個城市。顯然這是多關鍵字排序的經典題目。多關鍵字排序,即先比較第一關鍵字(錢數),若第一關鍵字相同,則比較第二關鍵字(城市數)。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+8;
const int MAXL=1e2+8;
const long long INF=0x3f3f3f3f3f3f3f3f;
struct Edge{int v;long long w1,w2;//w1:花費 w2:經過城市個數
};
bool in_que[MAXN];
long long dis[MAXN];
vector<Edge>adj[MAXN];
int n,s,t,cty[MAXL],sum[MAXN];
/*** cty[]: 每次航班的花費* dis[]: 起點到每個城市的最小花費* sum[]: 起點到每個城市的最少經過城市數量
**/
void spfa(int s){queue<int>que;dis[s]=0,sum[s]=0,in_que[s]=true,que.push(s);while(!que.empty()){int u=que.front();in_que[u]=false,que.pop();for(auto[v,w1,w2]:adj[u])if(dis[v]>dis[u]+w1||(dis[v]==dis[u]+w1&&sum[v]>sum[u]+w2)){//按照關鍵字優先級更新dis[v]=dis[u]+w1,sum[v]=sum[u]+w2;if(!in_que[v])in_que[v]=true,que.push(v);}}
}
int main(){cin>>s>>t>>n;for(int i=1,c,m;i<=n;i++){cin>>c>>m;for(int j=1;j<=m;j++){cin>>cty[j];for(int k=1;k<j;k++)adj[cty[k]].push_back({cty[j],c,j-k});}}memset(dis,INF,sizeof(dis));spfa(s);if(dis[t]>=INF)cout<<"-1 -1";else cout<<dis[t]<<" "<<sum[t];return 0;
}

3.4 牛奶管道

農民約翰的農場有一套老舊的管網,管網由 MMM 條管道(1≤M≤5001≤M≤5001M500)構成,用于將牛奶從谷倉運到儲奶罐。他想在明年移除和更新大部分管道,但他想原封不動地保留一條完整的路徑,這樣他仍然可以把牛奶從谷倉輸送到儲罐。管網由 NNN 個節點(1≤N≤5001≤N≤5001N500)組成,每個點都可以作為一組管道的端點。結點 111 是谷倉,結點 NNN 是儲罐。MMM 條雙向管道中的每一條都連接一對節點,并且都有一個延遲值(牛奶達到管的另一端的用時)和容量值(單位時間內可以穩定通過管道的牛奶量)。多條管道可以連接同一對節點。對于一條連接谷倉與儲罐的路徑,路徑的延遲等于沿途所有管道的延遲之和,路徑的容量等于沿途管道最小的容量(因為這是制約牛奶運送的“瓶頸”)。如果約翰通過一條延遲為 LLL、容量為 CCC 的管道運送 XXX 個單位的牛奶,需要的時間為 L+XCL+\frac{X}{C}L+CX?。給出約翰的管網結構,請幫助他選擇一條路徑,使得他從谷倉到儲罐運送 XXX 個單位牛奶的總時間最少。


SPFA 變形。原汁原味,十分經典。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,l,c;};
bool in_que[MAXN];
vector<Edge>adj[MAXN];
int n,m,x,dis[MAXN],lmts[MAXN];
//dis[]:起點到各節點的最小延遲總和
//lmts[]:所有管道的容量
void spfa(int s,int lmt){queue<int>que;dis[s]=0,in_que[s]=true,que.push(s);while(!que.empty()){int u=que.front();in_que[u]=false,que.pop();for(auto[v,l,c]:adj[u])if(c>=lmt&&dis[v]>dis[u]+l){//可以通過且時間更短dis[v]=dis[u]+l;if(!in_que[v])in_que[v]=true,que.push(v);}}
}
int main(){cin>>n>>m>>x;for(int i=1,u,v,l,c;i<=m;i++){cin>>u>>v>>l>>c;adj[u].push_back({v,l,c});adj[v].push_back({u,l,c});lmts[i]=c;}int ans=INF;for(int i=1;i<=m;i++){memset(dis,INF,sizeof(dis));memset(in_que,false,sizeof(in_que));spfa(1,lmts[i]);ans=min(ans,dis[n]+x/lmts[i]);}cout<<ans;return 0;
}

4. Dijkstra

4.1 路徑統計

“RP餐廳”(?)的員工素質就是不一般,在齊刷刷的算出同一個電話號碼之后,就準備讓 HZH,TZY 去送快餐了,他們將自己居住的城市畫了一張地圖,已知在他們的地圖上,有 NNN 個地方,而且他們目前處在標注為 111 的小鎮上,而送餐的地點在標注為 NNN 的小鎮。除此之外還知道這些道路都是單向的,從小鎮 IIIJJJ 需要花費 D[I,J] 的時間,為了更高效快捷的將快餐送到顧客手中,他們想走一條從小鎮 111 到小鎮 NNN 花費最少的一條路,但是他們臨出發前,撞到因為在路上堵車而生氣的 FYY,深受啟發,不能僅知道一條路線,萬一…于是,他們邀請 FYY 一起來研究起了下一個問題:這個最少花費的路徑有多少條?


顯然,我們需要新開一個數組 cnt[] 來存儲從起點到每個節點的最短路徑的數量(即輸出的第二個數據)。然后分兩種情況:

  • 情況 1:發現一條更短的路徑到達節點 vvv(即 dis[v]>dis[u]+w
    • 更新 dis[v] 為新的最短距離。
    • 重置 cnt[v]=cnt[u](因為到達 vvv 的最短路徑現在只能通過 uuu 實現,路徑數量與到達 uuu 的數量相同)。
  • 情況 2:當發現一條與當前最短路徑等長的路徑(即 dis[v]=dis[u]+w
    • 累加 cnt[v]+=cnt[u](到達 vvv 的最短路徑又多了 cnt[u] 條,這些路徑是通過 uuu 到達 vvv 的)。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
bool vis[MAXN];
vector<Edge>adj[MAXN];
int n,m,dis[MAXN],cnt[MAXN];
void dijkstra(int s){priority_queue<pair<int,int> >pq;dis[s]=0,cnt[s]=1,pq.push({0,s});while(!pq.empty()){auto u=pq.top().second;pq.pop();if(vis[u])continue;vis[u]=true;for(auto[v,w]:adj[u])if(dis[v]>dis[u]+w)dis[v]=dis[u]+w,cnt[v]=cnt[u],pq.push({-dis[v],v});else if(dis[v]==dis[u]+w)cnt[v]+=cnt[u];}
}
int main(){cin>>n>>m;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,adj[u].push_back({v,w});memset(dis,INF,sizeof(dis));dijkstra(1);if(dis[n]==INF)cout<<"No answer";else cout<<dis[n]<<" "<<cnt[n];return 0;
}

4.2 平面上的點

給定平面上的 nnn 個點,定義 (x1,y1)→(x2,y2)(x_1,y_1)\rightarrow(x_2,y_2)(x1?,y1?)(x2?,y2?) 的費用為它們的最小距離分量,求從 111 號點走到 nnn 號點的最小費用。


很顯然,有一個 O(n2)O(n^2)O(n2) 的建圖方法:

for(int u=1;u<=n;u++)for(int v=u+1;v<=n;v++){adj[u].push_back({v,min(abs(p[u].x-p[v].x),abs(p[u].y-p[v].y))});adj[v].push_back({u,min(abs(p[u].x-p[v].x),abs(p[u].y-p[v].y))});}

但可以考慮投影優化。

首先,將平面上的點 (x,y)(x, y)(x,y) 投射到 xxx 軸上,得到投影點 (x,0)(x, 0)(x,0),兩點的 xxx 軸投影距離為 ∣x1?x2∣|x_1 - x_2|x1??x2?(即橫向距離);然后,將平面上的點 (x,y)(x, y)(x,y) 投射到 yyy 軸上,得到投影點 (0,y)(0, y)(0,y),兩點的 yyy 軸投影距離為 ∣y1?y2∣|y_1 - y_2|y1??y2?(即縱向距離)。

接下來,利用投影的相鄰性質,即在同一投影軸上(如 xxx 軸),任意兩個非相鄰投影點的距離,等于它們之間所有相鄰投影點的距離之和。

然后,再利用 Dijkstra 等其他最短路算法的性質,我們知道,每次在計算起點到當前節點的距離的時候,都會處理出真正的最短距離。因此我們只需要把 ∣x1?x2∣|x_1-x_2|x1??x2?∣y1?y2∣|y_1-y_2|y1??y2? 一起放入鄰接矩陣即可。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
struct Point{int x,y,idx;}p[MAXN];
bool vis[MAXN];
int n,m,s,t,dis[MAXN];
vector<Edge>adj[MAXN];
bool cmpx(Point p1,Point p2){return p1.x<p2.x;}
bool cmpy(Point p1,Point p2){return p1.y<p2.y;}
void dijkstra(int s){priority_queue<pair<int,int> >pq;//{-dis[u],u}dis[s]=0,pq.push({0,s});while(!pq.empty()){auto u=pq.top().second;pq.pop();if(vis[u])continue;vis[u]=true;for(auto[v,w]:adj[u])if(dis[v]>dis[u]+w)dis[v]=dis[u]+w,pq.push({-dis[v],v});}
}
int main(){cin>>n;for(int i=1;i<=n;i++)cin>>p[i].x>>p[i].y,p[i].idx=i;sort(p+1,p+n+1,cmpx);for(int i=1;i<n;i++){adj[p[i].idx].push_back({p[i+1].idx,p[i+1].x-p[i].x});adj[p[i+1].idx].push_back({p[i].idx,p[i+1].x-p[i].x});}sort(p+1,p+n+1,cmpy);for(int i=1;i<n;i++){adj[p[i].idx].push_back({p[i+1].idx,p[i+1].y-p[i].y});adj[p[i+1].idx].push_back({p[i].idx,p[i+1].y-p[i].y});}memset(dis,INF,sizeof(dis));dijkstra(1);cout<<dis[n];return 0;
}

4.3 邀請函

在電視時代,沒有多少人觀看戲劇表演。 劇場老板意識到了這一事實,他想宣傳劇院,尤其是古典的喜劇片。
他已經打印了邀請函和所有必要的信息和計劃。許多學生被雇來分發這些邀請函。每個學生被指定了一個確切的公共汽車站,他或她將留在那里一整天,邀請人們觀看戲劇表演。
這里的公交系統是非常特殊的:共有 nnn 個站點和 mmm 個線路,所有的線路都是單向的,連接兩個站點。公共汽車離開起始點,到達目的地之后又空車返回起始點。
學生每天早上從總部所在的 111 號站點出發,乘公交車到一個預定的站點邀請乘客。每個站點都被安排了一名學生。在一天結束的時候,所有的學生都回到總部。現在需要知道的是,學生所需的公交費用的總和最小是多少。


見 3.1 奶牛派對(就在本篇博客)。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+8;
const int INF=0x3f3f3f3f;
struct Edge{int v,w;};
bool vis[MAXN];
vector<Edge>adj[2][MAXN];
int n,m,dis[MAXN],ans[MAXN];
void dijkstra(int s,int rev){memset(dis,INF,sizeof(dis));memset(vis,false,sizeof(vis));priority_queue<pair<int,int> >pq;dis[s]=0,pq.push({0,s});while(!pq.empty()){auto u=pq.top().second;pq.pop();if(vis[u])continue;vis[u]=true;for(auto[v,w]:adj[rev][u])if(dis[v]>dis[u]+w)dis[v]=dis[u]+w,pq.push({-dis[v],v});}
}
int main(){cin>>n>>m;for(int i=1,u,v,w;i<=m;i++)cin>>u>>v>>w,adj[0][u].push_back({v,w}),adj[1][v].push_back({u,w});dijkstra(1,0);for(int i=1;i<=n;i++)ans[i]=dis[i];dijkstra(1,1);long long ret=0;for(int i=1;i<=n;i++)ans[i]+=dis[i],ret+=ans[i];cout<<ret;return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/89879.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/89879.shtml
英文地址,請注明出處:http://en.pswp.cn/web/89879.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

“融合進化,智領未來”電科金倉引領數字化轉型新紀元

一、融合進化 智領未來電科金倉2025產品發布會重磅開啟&#xff01; 7月15日&#xff0c;以“融合進化 智領未來”為主題的電科金倉2025產品發布會在北京舉辦。產品發布會上展示了四款代表未來數字化趨勢的創新性產品。這些產品不僅涵蓋了數據庫技術&#xff0c;還涉及到數據集…

常規筆記本和加固筆記本的區別

在現代科技產品中&#xff0c;筆記本電腦因其便攜性和功能性被廣泛應用。根據使用場景和需求的不同&#xff0c;筆記本可分為常規筆記本和加固筆記本&#xff0c;二者在多個方面存在顯著區別。適用場景是區分二者的重要標志。常規筆記本主要面向普通消費者和辦公人群&#xff0…

Shell 腳本編程全面學習指南

前言Shell 腳本編程是 Linux 和 Unix 系統管理、自動化任務的核心工具之一。通過 Shell 腳本&#xff0c;你可以自動化重復性操作、簡化復雜流程、提高系統管理效率&#xff0c;甚至構建完整的自動化運維工具。本文將帶你從基礎到進階&#xff0c;全面學習 Shell 腳本編程&…

DelayQueue延遲隊列的使用

1、DelayQueue簡介 DelayQueue 也是 Java 并發包&#xff08;java.util.concurrent&#xff09;中的一個特殊隊列,用于在指定的延遲時間之后處理元素。 DelayQueue的一些關鍵特性&#xff1a; 延遲元素處理&#xff1a;只有當元素的延遲時間到期時&#xff0c;元素才能被取出…

QT6 源,七章對話框與多窗體(6) 顏色對話框 QColorDialog :本類的屬性,信號函數,靜態成員函數,以及源代碼

&#xff08;1&#xff09;本類的繼承關系如下 &#xff1a;&#xff08;2&#xff09; 對于本標準顏色對話框來講&#xff0c;學會使用其靜態函數以獲取到顏色就足夠了。&#xff08;3&#xff09; 開始學習本類的靜態成員函數 &#xff1a;&#xff08;4&#xff09;測試一下…

金倉數據庫:融合進化,智領未來——2025年數據庫技術革命的深度解析

引言 在數字中國戰略的推動下&#xff0c;數據庫作為數字經濟的基礎設施&#xff0c;正經歷著前所未有的技術重構。2025年7月15日&#xff0c;電科金倉以"融合進化&#xff0c;智領未來"為主題&#xff0c;發布了新一代數據庫產品矩陣&#xff0c;標志著國產數據庫在…

【人工智能99問】卷積神經網絡(CNN)的結構和原理是什么?(10/99)

文章目錄卷積神經網絡&#xff08;CNN&#xff09;的結構及原理一、CNN的核心結構1. 輸入層&#xff08;Input Layer&#xff09;2. 卷積層&#xff08;Convolutional Layer&#xff09;2. 卷積層的核心機制&#xff1a;局部感受野與權值共享3. 池化層&#xff08;Pooling Laye…

CCF編程能力等級認證GESP—C++7級—20250628

CCF編程能力等級認證GESP—C7級—20250628單選題&#xff08;每題 2 分&#xff0c;共 30 分&#xff09;判斷題&#xff08;每題 2 分&#xff0c;共 20 分&#xff09;編程題 (每題 25 分&#xff0c;共 50 分)線圖調味平衡單選題&#xff08;每題 2 分&#xff0c;共 30 分&…

《Python 類設計模式:屬性分類(類屬性 VS 實例屬性)與方法類型(實例 / 類 / 靜態)詳解》

Python 類和對象&#xff1a;從 "圖紙" 到 "實物" 的編程思維面向對象編程&#xff08;Object-Oriented Programming&#xff0c;簡稱OOP &#xff09;是一種通過組織對象來編程的方法。1.初識類和對象&#xff1a;用生活例子看透核心概念1.1類-class物與類…

Eureka服務端啟動

目錄 1、相關文章 2、創建eureka-server子工程 3、父工程build.gradle引入版本依賴管理 4、子工程build.gradle引入依賴 5、將main重命名為EurekaApplication并修改代碼 6、添加application.yml文件 7、啟動工程并訪問 8、訪問界面如下 9、 完整目錄結構 1、相關文章 …

AWS Partner: Sales Accreditation (Business)

AWS Partner: Sales Accreditation &#xff08;Business&#xff09;云概念和AWS云計算什么是云計算&#xff1f;計算的演變趨勢云計算部署模型AWS 客戶采用的模式為什么客戶選擇AWSAWS競爭優勢高可用的全球基礎設施AWS服務服務廣度和深度AWS產品和服務服務類別AWS解決方案庫A…

深入理解設計模式之中介者模式:解耦對象交互的利器

為什么需要中介者&#xff1f;在軟件開發中&#xff0c;我們經常會遇到對象之間需要相互通信的場景。當系統規模較小時&#xff0c;對象直接相互引用并通信可能不會帶來太大問題。但隨著系統復雜度增加&#xff0c;對象間的交互關系會變得錯綜復雜&#xff0c;形成一個復雜的網…

從 0 安裝 Label Studio:搭建可后臺運行的數據標注平臺(systemd 實踐

本文將介紹如何使用 pip 安裝 Label Studio&#xff0c;并通過 systemd 實現開機自啟與后臺運行&#xff0c;適用搭建個人項目的數據標注平臺。 一、Label Studio 簡介 Label Studio 是一個開源、跨模態的數據標注工具&#xff0c;支持文本、圖像、音頻、視頻、HTML等多種類型…

【數據結構】鏈表(linked list)

目錄 一、鏈表的介紹 二、單鏈表 1. 單鏈表的初始化 2. 單鏈表的插入 &#xff08;1&#xff09;動態申請一個節點 &#xff08;2&#xff09;頭插法 &#xff08;3&#xff09;尾插法 &#xff08;4&#xff09;按照位置來插入 &#xff08;5&#xff09;在地址之前插…

反序列化漏洞1-PHP序列化基礎概念(0基礎超詳細)

一.PHP序列化基礎概念首先當我們看到反序列化漏洞這個概念&#xff0c;我們的第一個問題是什么是反序列化&#xff1f;那么我們要知道什么是反序列化就要知道什么是序列化。序列化就是可以將一個對象壓縮并格式化成字符串&#xff0c;可以將該對象保存下來&#xff0c;以便存儲…

【微服務】Ocelot微服務網關

目錄 一、目的 二、Ocelot介紹 三、.Net中使用Ocelot搭建網關服務 3.1 搭建網關Ocelot步驟 3.1.1、創建Net7 WebApi服務 3.1.2、Nuget引入-Ocelot程序包&#xff08;版本&#xff1a;19.0.2&#xff09; 3.1.3、配置中間件和IOC注冊 3.1.4 配置文件編輯Ocelot網關配置信…

零基礎入門:用按鍵精靈實現視頻自動操作(附完整腳本)

摘要&#xff1a;本文手把手教你編寫視頻平臺的自動化腳本&#xff0c;涵蓋點擊、循環、防檢測等核心技巧&#xff0c;無需編程基礎&#xff0c;輕松實現自動播放/點贊/跳過廣告。&#xff08;使用按鍵精靈2024版演示&#xff09; 一、應用場景 自動化操作&#xff1a;自動跳過…

AI(學習筆記第六課) 使用langchain進行AI開發 load documents(csv和文件夾)

文章目錄AI(學習筆記第六課) 使用langchain進行AI開發 load documents(csv和文件夾)學習內容&#xff1a;1.load documents&#xff08;csv&#xff09;1.1 學習url1.2 load csv文件1.2.1 默認load1.2.2 csv文件內容1.2.2 執行csv文件的load1.3 Customizing the CSV parsing an…

企業運維實戰:Jenkins 依賴 JDK21 與應用需 JDK1.8 共存方案(含流水線配置)

前言&#xff1a;在企業運維中&#xff0c;“工具升級”與“業務兼容”的平衡始終是核心挑戰。近期我們遇到一個典型場景&#xff1a;Jenkins 升級到 2.450 版本后&#xff0c;強制要求 JDK21 運行環境&#xff1b;但開發團隊的應用程序因框架依賴&#xff0c;必須使用 JDK1.8 …

爬蟲小知識三:selenium庫

前言 selenium 庫是一種用于 Web 應用程序測試的工具&#xff0c;它可以驅動瀏覽器執行特定操作&#xff0c;自動按照腳本代碼做出單擊、輸入、打開、驗證等操作&#xff0c;支持的瀏覽器包括 IE、Firefox、Safari、Chrome、Opera 等。 與 requests 庫不同的是&#xff0c;se…