1. 程式人生 > >【CCF 201609-4】 交通規劃 (圖論--Dijkstra)

【CCF 201609-4】 交通規劃 (圖論--Dijkstra)

CCF 201609-4 交通規劃                                                                  

問題描述

  G國國王來中國參觀後,被中國的高速鐵路深深的震撼,決定為自己的國家也建設一個高速鐵路系統。
  建設高速鐵路投入非常大,為了節約建設成本,G國國王決定不新建鐵路,而是將已有的鐵路改造成高速鐵路。現在,請你為G國國王提供一個方案,將現有的一部分鐵路改造成高速鐵路,使得任何兩個城市間都可以通過高速鐵路到達,而且從所有城市乘坐高速鐵路到首都的最短路程和原來一樣長。請你告訴G國國王在這些條件下最少要改造多長的鐵路。

輸入格式

  輸入的第一行包含兩個整數n, m,分別表示G國城市的數量和城市間鐵路的數量。所有的城市由1到n編號,首都為1號。
  接下來m行,每行三個整數a, b, c,表示城市a和城市b之間有一條長度為c的雙向鐵路。這條鐵路不會經過a和b以外的城市。

輸出格式

  輸出一行,表示在滿足條件的情況下最少要改造的鐵路長度。

樣例輸入

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

樣例輸出

11

評測用例規模與約定

  對於20%的評測用例,1 ≤ n ≤ 10,1 ≤ m ≤ 50;
  對於50%的評測用例,1 ≤ n ≤ 100,1 ≤ m ≤ 5000;
  對於80%的評測用例,1 ≤ n ≤ 1000,1 ≤ m ≤ 50000;
  對於100%的評測用例,1 ≤ n ≤ 10000,1 ≤ m ≤ 100000,1 ≤ a, b ≤ n,1 ≤ c ≤ 1000。輸入保證每個城市都可以通過鐵路達到首都。

  【題意】

   “所有城市乘坐高速鐵路到首都的最短路程和原來一樣長”,說明結果滿足單源最短路徑;“最少要改造多少鐵路”,說明是要在最短路徑中找最小花費。如果同時存在多條最短路徑,應該選擇擴充套件時用到的花費最小的那一條。
 【舉例說明】

  如下圖的例子所示,點1到點3的最短路徑是4,要連通點3,1-2-3、1-3和1-4-3都是最短路徑。但是如果選1-3,需要增加的鐵軌為4個單位;選1-2-3需要增加的鐵軌為2個單位;而選1-4-3的話,需要增加的鐵軌只有1個單位。所以此時應該選最後一種方案。

 【型別】
  Dijkstra演算法+貪心法
 【分析】
  可套用Dijkstra演算法,但是需要做一些改進。本題中

我們用Dijkstra演算法不是要得到最短路徑,而是要得到最短路徑下連通每個點所增加的最小的邊是多少,即在最短路徑中找最小花費。如果用cost[v]表示連通v點所增加的邊的權重,比如上圖中cost[3]=1。當遇到上述多種選項時,也就是dist[v]==disto[u]+cost時,讓cost[v]=min(cost[v],cost),這樣最終得到的cost[v]就是滿足最短路徑條件下的最小花費。

int cost[nmax]; //接通該點需要增加的邊的權重
//遍歷從x出發的所有邊(x,y),更新d[y]=min{d[y],d[x]+w[x,y]}
            for(int i=0;i<G[u].size();i++){
                Edge& e=edges[G[u][i]];//得到從u起點出發到i節點的一條邊e

                if(d[e.to]>d[u]+e.dist){
                    d[e.to]=d[u]+e.dist;
                    //p[e.to]=G[u][i];
                    q.push(HeapNode(d[e.to],e.to));//新增節點y

                    cost[e.to]=e.dist;//更新連通e.to點所增加的邊的權重為e.dist
                }
                //記錄下邊權最小的一個
               /*if(d[e.to]==d[u]+e.dist && cost[e.to]>e.dist)//可以這麼寫
                    cost[e.to]=e.dist;*/
                if(d[e.to]==d[u]+e.dist)
                   cost[e.to]=min(cost[e.to],e.dist);
            }

 【注意】

    1、切記:nmax節點數量的最大值要記得改!!!本題中n的最大值為10000,剛開始模板中const int nmax=100+5,宣告的namx不夠大,提交之後會超時的,提交了好多次才發現這個問題!

     2、本題目中新定義了一個cost陣列,記得和他有關的要初始化!

for(int i=0;i<n;i++) {
            d[i]=INF;
            cost[i]=INF;//初始化cost

        }
        d[s]=cost[s]=0;//記得源點從0開始,不是1開始

 【時間複雜度&&優化】
  O(nlog n)

#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
#include <cstdio>

const int nmax=10000+5;
#define INF 1e8
int n,m;
using namespace std;

struct HeapNode{
    int d,u;//d為s到各個節點的距離,u為起點
    HeapNode(){}
    HeapNode(int d,int u):d(d),u(u){}
    bool operator <(const HeapNode &rhs)const{//自定義greater運算元,優先輸出d小的節點
        return d>rhs.d;
    }
};
struct Edge{
    int from,to,dist;
    Edge(){}
    Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
struct Dijkstra{
    int n,m; //n為點數,m為邊數
    vector<Edge> edges;//邊列表,儲存各邊的編號
    vector<int>G[nmax];//鄰接表,儲存每個節點出發的邊編號(從0開始編號),G[u][i]為起點u到節點i的邊的編號
    bool done[nmax];//是否已永久標號
    int d[nmax];//源點s到各個節點的距離,id[i]為源點s到節點i的距離
    int p[nmax];//最短路中的上一條弧,p[i]為源點s到節點i的最短路中的最後一條邊的編號

    int cost[nmax]; //接通該點需要增加的邊的權重
    Dijkstra(){} //記得寫個空的建構函式,要不然DJ會報錯

    void init(int n){
        this->n=n;
        for(int i=0;i<n;i++){
            G[i].clear();//清空鄰接表
        }
        edges.clear();//清空邊列表
    }

    void AddEdge(int from,int to,int dist){//如果是無向圖,每條無向邊呼叫兩次AddEdge
       edges.push_back(Edge(from,to,dist));//邊列表增加一條邊
        m=edges.size();//邊列表中有幾條邊
        G[from].push_back(m-1);//鄰接表中的節點數目(下標從0開始)
    }

    void dijkstra(int s){//求源點0到其他節點的最短路徑
        for(int i=0;i<n;i++) {
            d[i]=INF;
            cost[i]=INF;//初始化cost

        }
        d[s]=cost[s]=0;//記得源點從0開始,不是1開始
         //本題中,源點為節點0
        memset(done,0, sizeof(done));

        priority_queue<HeapNode>q;
        q.push(HeapNode(d[s],0));//本題中優先佇列初始化的節點為HeapNode(d[0],0)

        while(!q.empty()){
            //在所有未標號的節點中,選出d值最小的節點
            HeapNode x=q.top();//找到d值最大的節點
            q.pop();//彈出隊頭
            int u=x.u;//得到d值最大的節點的起點
            //給節點x標記
            if(done[u]) continue; //如果當前節點已經標號,則continue
            done[u]=1; //若未標號,則標記
            //遍歷從x出發的所有邊(x,y),更新d[y]=min{d[y],d[x]+w[x,y]}
            for(int i=0;i<G[u].size();i++){
                Edge& e=edges[G[u][i]];//得到從u起點出發到i節點的一條邊e

                if(d[e.to]>d[u]+e.dist){
                    d[e.to]=d[u]+e.dist;
                    //p[e.to]=G[u][i];
                    q.push(HeapNode(d[e.to],e.to));//新增節點y

                    cost[e.to]=e.dist;//更新連通e.to點所增加的邊的權重為e.dist
                }
                //記錄下邊權最小的一個
               /*if(d[e.to]==d[u]+e.dist && cost[e.to]>e.dist)
                    cost[e.to]=e.dist;*/
                if(d[e.to]==d[u]+e.dist)
                   cost[e.to]=min(cost[e.to],e.dist);
            }
        }
    }
}DJ;
int main() {
    while(scanf("%d%d",&n,&m)==2){//輸入節點數

        DJ.init(n);//切記要初始化,清空邊列表和鄰接表
        while(m--) {
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);//輸入節點是從1開始的
            u--;v--;//記得把輸入節點調整成0開始的
            DJ.AddEdge(u,v,d);
            DJ.AddEdge(v,u,d);
        }

        DJ.dijkstra(0);//得到節點0到其他節點的最短路徑長度

        int ans=0;
        for(int i=1;i<=n;i++)
           ans+=DJ.cost[i];

        //cout<<ans<<endl;//求節點0到節點n-1的最短距離
        printf("%d\n",ans);

    }
    return 0;
}