問題描述
試題編號: | 201609-4 |
試題名稱: | 交通規劃 |
時間限制: | 1.0s |
內存限制: | 256.0MB |
問題描述: |
問題描述 G國國王來中國參觀後,被中國的高速鐵路深深的震撼,決定爲自己的國家也建設一個高速鐵路系統。 輸入格式 輸入的第一行包含兩個整數n, m,分別表示G國城市的數量和城市間鐵路的數量。所有的城市由1到n編號,首都爲1號。 輸出格式 輸出一行,表示在滿足條件的情況下最少要改造的鐵路長度。 樣例輸入 4 5 樣例輸出 11 評測用例規模與約定 對於20%的評測用例,1 ≤ n ≤ 10,1 ≤ m ≤ 50; |
求解思路:
這道題可以抽象爲:首節點1到其餘n-1各節點的距離均爲最短,且要求所有路徑之和(去掉重複的段)最小。顯然這道題需要求單源最短路徑,故想到dijistra算法,但是此題還要求在保證各節點到原點距離最小的前提下,所有路徑之和(去掉重複段)最小,我們知道,節點1到各個節點的最短路徑可能不止一條,那麼我們怎麼滿足路徑之和最小呢?不防這樣考慮:任意一種情況下(滿足節點1到各節點是最短路徑),路徑之和等於各節點的入邊長度之和,因此,我們可以在dijistra算法的鬆弛條件出增加一個判斷,即:當Dis[next]==Dis[newp]+cost時(遇到了兩種路徑長度一樣的情況),選擇兩種情況中cost較小的,因此需要用一個數組來保存每個節點的入邊大小,設該數組爲Cost[]。在dijistra算法執行過程中,Cost數組不斷被更新。最後只需要把Cost數組的值相加求和,即爲最終答案。
算法:
在使用Dijistra算法前,我們首先要考慮節點總數,如果節點數小於100000,那麼可以使用普通的Dijistra,否則的話,算法的時間複雜度很有可能超出限制,這時應該選擇帶有堆優化的Dijistra算法;
Dijistra算法的機制:
已知的最短路徑爲:只有一個節點1,路徑長度爲0,那麼遍歷節點1的所有鄰接節點,則這些鄰接節點到節點1的最短路徑一定爲1到這些節點,從這些路徑中選擇一個最小的,那麼這個最小的路徑的邊緣節點繼續遍歷其鄰接節點,又能得到這些鄰接節點到節點1的最短路徑,以此類推;
普通算法:
#include<stdio.h>
#include<stdlib.h>
#include<vector>
using namespace std;
typedef struct E{
int next;
int cost;
}E;
vector<E> edge[10001];
bool mark[10001];
int Dis[10001];
int Cost[10001];
int main()
{
int n,m,a,b,c;
int res=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
edge[i].clear();
mark[i]=false;
Dis[i]=-1;
Cost[i]=0;
}
while(m--)
{
scanf("%d%d%d",&a,&b,&c);
E tmp;
tmp.next=b;
tmp.cost=c;
edge[a].push_back(tmp);
tmp.next=a;
edge[b].push_back(tmp);
}
int newp=1;
Dis[newp]=0;
for(int i=0;i<n;i++)
{
int min=0x7fffffff,newp;
for(int j=1;j<=n;j++)//找當前所有路徑中的最小值
{
if(mark[j]) continue;
if(Dis[j]==-1) continue;
if(Dis[j]<min)
{
min=Dis[j];
newp=j;
}
}
mark[newp]=true;
for(int j=0;j<edge[newp].size();j++)
{
int next=edge[newp][j].next;
int cost=edge[newp][j].cost;
if(mark[next]) continue;
if(Dis[next]==-1||Dis[newp]+cost<Dis[next])
{
Dis[next]=Dis[newp]+cost;
Cost[next]=cost;
}
else if(Dis[newp]+cost==Dis[next])//兩種情況的最短路徑均相等時
{
if(cost<Cost[next])//確保入邊長度更小的在數組中
{
Cost[next]=cost;
}
}
}
}
for(int i=1;i<=n;i++) res+=Cost[i];
printf("%d\n",res);
return 0;
}
使用堆優化的Dijistra:
#include<stdio.h>
#include<stdlib.h>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
typedef struct E{
int next;
int cost;
}E;
typedef struct D{
int dis;
int id;
bool operator <(const D &A)const{//重載小於號,爲了構造小根堆(即:路徑越小,值越大)
return dis>A.dis;
}
}D;
vector<E> edge[10001];
bool mark[10001];
int Dis[10001];
int Cost[10001];
int main()
{
int n,m,a,b,c;
int res=0;
priority_queue<D> pq;//構造小根堆
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
edge[i].clear();
mark[i]=false;
Dis[i]=-1;
Cost[i]=0;
}
while(m--)
{
scanf("%d%d%d",&a,&b,&c);
E tmp;
tmp.next=b;
tmp.cost=c;
edge[a].push_back(tmp);
tmp.next=a;
edge[b].push_back(tmp);
}
int newp=1;
Dis[newp]=0;
D tmp;
tmp.dis=0;
tmp.id=1;
pq.push(tmp);//節點1進入小根堆
while(!pq.empty())
{
newp=pq.top().id;
pq.pop();//從堆頂取走節點後,要將堆頂節點pop出去,否則該節點總是最小的
if(mark[newp]) continue;//已經被選中的就不要了
mark[newp]=true;
for(int j=0;j<edge[newp].size();j++)
{
int next=edge[newp][j].next;
int cost=edge[newp][j].cost;
if(mark[next]) continue;
if(Dis[next]==-1||Dis[newp]+cost<Dis[next])//如果滿足更新能力
{
Dis[next]=Dis[newp]+cost;
D dtmp;
dtmp.dis=Dis[next];
dtmp.id=next;
pq.push(dtmp);
Cost[next]=cost;
}
else if(Dis[next]==Dis[newp]+cost)
{
if(cost<Cost[next])
{
Cost[next]=cost;
}
}
}
}
for(int i=1;i<=n;i++) res+=Cost[i];
printf("%d\n",res);
return 0;
}
注意:
Dijistra算法如果不用堆優化,其時間複雜度爲O(n*n),這是因爲dijistra的機制是對當前n各節點的路徑長度進行遍歷,選出一個最小的且沒有被選中的,然後遍歷這個最小路徑的邊緣節點的所有鄰接節點,每次循環都可以選出一個新的被選中節點,因此需要循環n次,不難看出大循環中遍歷鄰接節點的循環只取決於邊緣節點的鄰接節點數,其時間複雜度很小,而從n個節點中選出一個路徑最小的過程,其事件複雜度爲n,佔主要;因此堆優化的目的就是爲了以更小的時間複雜度選出這一具有最小路徑的節點。因此用STL模板中的優先隊列,構造一個小根堆,我們知道構造並從小根堆中搜索最小值的時間複雜度僅爲O(logn),故堆優化可以使Dijistra的時間複雜度降到O(n*logn)。
運行結果: