1. 程式人生 > 其它 >分層圖最短路(Magical Girl Haze & 飛行路線)

分層圖最短路(Magical Girl Haze & 飛行路線)

ICPC2018南京網路賽. Magical Girl Haze

  • 題意:所有的分層圖最短路題意基本都一樣,給出k次機會將兩個點之間的距離/花費變為0,求起始點到終點最短距離/花費,這道題是有向圖,別搞得跟我似的沒看清題目就瞎存,最後找bug找了兩個小時才找出來。
  • 題解:先安利一波洛谷各大神仙的題解,我大概就是從這學的:各種題解
    接著自己總結:既然有k次機會可以使用,那可以將原來的圖建k+1層(從第0層開始到第k層,每一層的頂點關係都與第0層一致),第i層代表使用免費機會的次數,然後在跑最短路的時候用到dp的思想:
    1. 若此時免費次數沒使用完,並且第t層的結點u到起始點(第0層的s)的最小距離dis[u][t] < 第t+1層結點v(u與v相鄰)到起始點(第0層的s)的最小距離dis[v][t+1]那就更新第t+1層結點v到起始點的最小距離;
    2. 若免費次數已經用完,但是此時並且第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;
}