【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演算法,但是需要做一些改進。本題中
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;
}