问题描述
试题编号: | 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)。
运行结果: