最短路專題2 | CodeForces 449B - SPFA算法

最短路專題2 | CodeForces 449B - SPFA算法

B. Jzzhu and Cities

Jzzhu is the president of country A. There are n cities numbered from 1 to n in his country. City 1 is the capital of A. Also there are m roads connecting the cities. One can go from city uiu_i to viv_i (and vise versa) using the ii-th road, the length of this road is xix_i. Finally, there are k train routes in the country. One can use the ii-th train route to go from capital of the country to city sis_i (and vise versa), the length of this route is yiy_i.
J是A國的總統,這個國家有n個城市。1是首都,有m條公路連接這些城市。然後,有k個火車線。城市sis_i到首都1的距離是yiy_i

Jzzhu doesn’t want to waste the money of the country, so he is going to close some of the train routes. Please tell Jzzhu the maximum number of the train routes which can be closed under the following condition: the length of the shortest path from every city to the capital mustn’t change.
J不想浪費錢,刪除部分鐵路,但是每個城市到達首都的最短路徑不能被修改,也就是說,只有最短路徑不經過這個鐵路時,纔可以刪除。

Input

The first line contains three integers n,m,k(2n105;1m3105;1k105)n, m, k (2 ≤ n ≤ 105; 1 ≤ m ≤ 3·105; 1 ≤ k ≤ 105).

Each of the next mm lines contains three integers ui,vi,xi(1ui,vin;uivi;1xi109)u_i, v_i, x_i (1 ≤ u_i, v_i ≤ n; u_i ≠ v_i; 1 ≤ x_i ≤ 109).

Each of the next kk lines contains two integers sis_i and yi(2sin;1yi109)y_i (2 ≤ si ≤ n; 1 ≤ yi ≤ 109).

It is guaranteed that there is at least one way from every city to the capital. Note, that there can be multiple roads between two cities. Also, there can be multiple routes going to the same city from the capital.
保證至少有一條路從每個城市到達首都。注意,2個城市之間可能有多條路線。

Output

Output a single integer representing the maximum number of the train routes which can be closed.
火車線被關閉的數量

Examples

input

5 5 3
1 2 1
2 3 2
1 3 3
3 4 4
1 5 5
3 5
4 5
5 5

output

2

input

2 2 3
1 2 2
2 1 3
2 1
2 2
2 3

output

2

解題思路:

題意大概是說有多個城市,每個城市都和首都連接,有可能是公路,也有可能是鐵路。爲了削減開支,總統大人希望能夠將現有的鐵路拆掉,但是必須有一個前提,那就是拆了之後用戶可以通過公路到達首都。而且路程不能比以前鐵路的路程遠。

這個題目看起來就是一道最短路徑問題,不管是公路還是鐵路,都將加入到圖G中。然後求出每個城市到達首都的最短路徑,在這個過程中,如果路徑中存在鐵路,就看下鐵路能否用公路代替(看能否通過其他路線進行鬆弛處理),也就是說,在鬆弛的過程中,如果對象是鐵路,則標記一下這個鐵路可以被替換掉。

顯然,這裏提到了鬆弛操作,有同學就大概明白了會使用bellman-ford或者SPFA算法,因爲BF算法就是通過不停的鬆弛處理來計算最短路徑的。

本篇採用SPFA算法來處理這個問題。

SPFA算法性能通常要比BF好很多,不過最壞複雜度還是O(VE)O(VE)

SPFA算法的全稱是:Shortest Path Faster Algorithm。

注意:爲了避免最壞情況的出現,在正權圖上應使用效率更高的Dijkstra算法。

如果一個點進入隊列達到n次,則表明圖中存在負環,沒有最短路徑。

SPFA算法思路整理

SPFA算法是bellman-ford算法的改進版本,將之前的循環遍歷改成了FIFO隊列,然後通過優化的方式,提升性能。

一開始放入節點1到隊列Q,然後開始循環,只要Q裏面元素還存在,就不退出。
每次都從Q裏面取得一個元素u,循環遍歷u的相鄰節點v,
如果v的d可以更新,也就是鬆弛判定,那麼就將v加入到隊列中。

SPFA優化策略

SPFA算法有兩個優化策略SLF和LLL——SLF:Small Label First 策略,設要加入的節點是j,隊首元素爲i,若dist(j)<dist(i),則將j插入隊首,否則插入隊尾; LLL:Large Label Last 策略,設隊首元素爲i,隊列中所有dist值的平均值爲x,若dist(i)>x則將i插入到隊尾,查找下一元素,直到找到某一i使得dist(i)<=x,則將i出隊進行鬆弛操作。SLF 和 LLF 在隨機數據上表現優秀,但是在正權圖上最壞情況爲 O(VE),在負權圖上最壞情況爲達到指數級複雜度。

源代碼 C++

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <deque>
#define LL long long

#define inf 0x3ffffffffffffffll

using namespace std;

int GetInt() {
    int ret, ch;
    while (!isdigit(ch = getchar()));

    ret = ch - '0';
    while (isdigit(ch = getchar()))
        ret = (ret << 1) + (ret << 3) + ch - '0';
    return ret;
}

LL GetLL() {
    return (LL)GetInt();
}

const int maxn = 111111, maxm = 600666;

struct {
    int to, nxt;
    LL w;
} E[maxm];

int zh[maxn], tot = 0;

void AddEdge(int u, int v, LL w) {
    E[++tot].to = v;
    E[tot].nxt = zh[u];
    E[tot].w = w;
    zh[u] = tot;
}

LL d[maxn];
bool inq[maxn] = {};
bool T[maxn] = {};

deque<int> q;
int n, m, k;

void SPFA() {
    while (!q.empty()) {
        // 從第一個元素開始
        int u = q.front();
        q.pop_front();

        //是否在隊列中,防止重複進入隊列
        inq[u] = 0;

        //遍歷節點u的相鄰的節點
        for (int i = zh[u]; i; i = E[i].nxt) {
            //相鄰節點v
            int v = E[i].to;

            //查看是否需要鬆弛
            LL D = d[u] + E[i].w;

            if (D <= d[v] && T[v]){
                // 如果是火車的城市鬆弛,標記火車爲0
                T[v] = 0;
            }

            //如果需要鬆弛
            if (D < d[v]) {
                //更新d值
                d[v] = D;
                //將相鄰的節點加入到隊列中,防止重複判斷
                if (!inq[v]) {
                    //標記已經在隊列中
                    inq[v] = 1;

                    //優化,放在隊列前面還是後面
                    if (d[v] <= d[q.size() ? q.front() : 0])
                        q.push_front(v);
                    else
                        q.push_back(v);
                }
            }
        }
    }
}

int main() {
    while (!q.empty())
        q.pop_front();
    cin >> n >> m >> k;

    // m個公路
    for (int i = 1; i <= m; i++) {
        int a, b;
        LL c;
        a = GetInt();
        b = GetInt();
        c = GetLL();
        AddEdge(a, b, c);
        AddEdge(b, a, c);
    }

    memset(d, 0x3f, sizeof(d));
    d[1] = 0;
    //插入位置1
    q.push_back(1);
    inq[1] = 1;

    // k個鐵路
    for (int i = 1; i <= k; i++) {
        // 輸入a、b
        int a = GetInt();
        //距離b
        LL b = GetLL();

        d[a] = min(d[a], b);

        // 如果a不在隊列中,則將a放入隊列
        if (!inq[a]) {
            q.push_back(a);
            inq[a] = 1;
        }
        
        //標記城市a爲有火車的
        T[a] = 1;
    }

    // 開始spfa最短路算法
    SPFA();

    //當前還剩的火車數量
    int nowC = 0;
    for (int i = 1; i <= n; i++)
        if (T[i])
            nowC++;
    //輸出去掉的火車數量
    cout << k - nowC << endl;
    return 0;
}

如果用Dijkstra算法?

上面是SPFA算法實現的,但是如果想用Dijkstra算法呢?畢竟這裏也沒有負權的邊,答案是肯定的,Dijkstra算法雖然不是鬆弛的做法,回顧上一篇,Dijkstra算法是分2個圖,每次取一個最短d的邊加入最短路中,本題使用Dijkstra可以先用Dijkstra算出最短路徑,然後看下每一個城市到達首都的距離是不是變小了,如果變了,就說明這個首都到達這個城市的鐵路可以拆掉。Dijkstra+優先隊列的性能也是不輸SPFA的,而且更加穩定。

結語

嗯,這兩篇大概講了Dijkstra和SPFA算法,接下來一篇是求負權最短路的題目,下期再見~~

個人公衆號:ACM算法日常

專注於基礎算法的研究工作,深入解析ACM算法題,五分鐘閱讀,輕鬆理解每一行源代碼。內容涉及算法、C/C++、軟件設計等。

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章