分層圖最短路(Magical Girl Haze & 飛行路線)
阿新 • • 發佈:2021-07-15
ICPC2018南京網路賽. Magical Girl Haze
- 題意:所有的分層圖最短路題意基本都一樣,給出k次機會將兩個點之間的距離/花費變為0,求起始點到終點最短距離/花費,這道題是有向圖,別搞得跟我似的沒看清題目就瞎存,最後找bug找了兩個小時才找出來。
- 題解:先安利一波洛谷各大神仙的題解,我大概就是從這學的:各種題解。
接著自己總結:既然有k次機會可以使用,那可以將原來的圖建k+1層(從第0層開始到第k層,每一層的頂點關係都與第0層一致),第i層代表使用免費機會的次數,然後在跑最短路的時候用到dp的思想:- 若此時免費次數沒使用完,並且第t層的結點u到起始點(第0層的s)的最小距離dis[u][t] < 第t+1層結點v(u與v相鄰)到起始點(第0層的s)的最小距離dis[v][t+1]那就更新第t+1層結點v到起始點的最小距離;
- 若免費次數已經用完,但是此時並且第t層的結點u到起始點(第0層的s)的最小距離dis[u][t] < 第t層結點v(u與v相鄰)到起始點(第0層的s)的最小距離dis[v][t],那相當於此時在同一層進行轉移,需要更新第t層結點v到起始點的最小距離。(洛谷題解裡面那個圖挺生動形象的~)
- 程式碼:
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; typedef long long ll; const int N = 1e5 + 5; const int M = 2e5 + 5; const int H = 13; ll t, n, m, k; ll head[N], vis[N][H], dis[N][H]; int cnt = 0, s = 1; //s:起點 struct Edge { ll to, w, next; }e[M*2]; //無向圖邊數*2(其實這是有向圖...) struct State { ll dis, u, t; //花費/距離、頂點編號、層號 // State(ll dis, ll u, ll t) // { // this -> dis = dis; // this -> u = u; // this -> t = t; // } friend bool operator < (State a, State b) //過載 { return a.dis > b.dis; } }; void add(int u, int v, int w) { e[++cnt].next = head[u]; e[cnt].to = v; e[cnt].w = w; head[u] = cnt; } void dijkstra() { dis[s][0] = 0; //起初為第0層,無需消耗k次的免費機會 priority_queue<State> q; q.push({0, s, 0}); while(q.size()) { State s = q.top(); q.pop(); ll node = s.u, t = s.t; if(vis[node][t]) continue; vis[node][t] = 1; for(ll i = head[node]; i != -1; i = e[i].next) { ll v = e[i].to, w = e[i].w; if(t < k && dis[node][t] < dis[v][t + 1]) //使用免費通行次數 { dis[v][t+1] = dis[node][t]; q.push({dis[v][t + 1], v, t + 1}); } if(dis[node][t] + w < dis[v][t]) //不使用免費通行次數 { dis[v][t] = dis[node][t] + w; q.push({dis[v][t], v, t}); } } } } int main() { scanf("%d", &t); while(t --) { cnt = 0; memset(head, -1, sizeof head); memset(vis, 0, sizeof vis); memset(dis, 0x3f, sizeof dis); scanf("%lld%lld%lld", &n, &m, &k); for(int i = 1; i <= m; i++) { //要去重邊?(用鄰接表存圖不用去) ll u, v, w; scanf("%lld%lld%lld", &u, &v, &w); add(u, v, w); } dijkstra(); ll res = 1e15; for(int i = 0; i <= k; i++) res = min(res, dis[n][i]); printf("%lld\n", res); } return 0; } /* 不取min的hack資料 1 3 2 5 1 2 3 2 3 4 */
JLOI2011.飛行路線
- 解析:題意都是一樣的,讀邊的時候習慣從1開始,就進行+1罷了。但是值得注意的是,其實上一題資料有點水,最後其實輸出dis[n][k]也能過掉,但是這題資料加強,那麼為什麼需要最後將使用0次免費機會-> k次機會的結果取一次最小值呢,按照貪心的策略不應該是給的免費機會越多,最後的最小值肯定越小嗎?
- 原因:因為若用這種dp轉移的方式做分層圖最短路,那麼當免費次數十分富餘時,這時候可能可以使所有道路都免費,那此時的情況將可以視作:用了 k-1(小於k) 次,已達目的地,為了使用最後一次(剩下的)免費通行的權利,先免費跑到另一個地方,結果發現不能免費回來到終點,那豈不是很翻車...所以取min嚴謹一些。
- 程式碼:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const int M = 2e5 + 5;
const int H = 15;
int dis[N][H], head[N], vis[N][H];
int n, m, k, s, t, cnt = 0;
struct State
{
int dis, u, t;
friend bool operator < (State a, State b)
{
return a.dis > b.dis;
}
};
struct Edge
{
int next, to, w;
}e[M*2];
void add(int u, int v, int w)
{
e[++cnt].next = head[u];
e[cnt].to = v;
e[cnt].w = w;
head[u] = cnt;
}
void dijkstra()
{
memset(dis, 0x3f, sizeof dis);
dis[s][0] = 0;
priority_queue<State> q;
q.push({0, s, 0});
while(q.size())
{
State node = q.top();
q.pop();
int u = node.u, t = node.t;
if(vis[u][t]) continue;
vis[u][t] = 1;
for(int i = head[u]; i != -1; i = e[i].next)
{
int v = e[i].to, w = e[i].w;
if(t < k && dis[u][t] < dis[v][t + 1])
{
dis[v][t + 1] = dis[u][t];
q.push({dis[v][t + 1], v, t + 1});
}
if(dis[u][t] + w < dis[v][t])
{
dis[v][t] = dis[u][t] + w;
q.push({dis[v][t], v, t});
}
}
}
}
int main()
{
memset(head, -1, sizeof head);
scanf("%d%d%d%d%d", &n, &m, &k, &s, &t);
s++, t++;
for(int i = 1; i <= m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
u++, v++;
add(u, v, w);
add(v, u, w);
}
dijkstra();
int res = 0x3f3f3f3f;
for(int i = 0; i <= k; i++) res = min(res, dis[t][i]);
printf("%d\n", res);
return 0;
}