CSP 201903-5(317号子任务 100分) 优先队列+dijkstra

题面

题目传送门
在这里插入图片描述

优化过程

在这里插入图片描述

思路

最短路径问题,可用Floyd,dijkstra,SPFA等解决,主要是后续优化。n值比较大且题目中明确说了有重边或自环,故不用邻接矩阵存图。用邻接表可以满足空间开销且避免了重边或自环带来的麻烦。Dijkstra通过优先队列优化后总复杂度为O(mlog2n),是最高效的最短路径算法。此题一个很关键的地方是计算距离时只计算发动机据点到各点的距离并保存下来,求其他点的最短路径时直接使用即可。求每个据点结果时,先排除不可达点,把其到剩下重要据点的距离放进数组,用sort按从小到大排序,把前k个或全部相加即是答案。最后时间还是用了将近800ms,距离要求的1s很接近,有点危险。不知道先取重边中最小值能不能进一步缩短时间,但感觉也没有减少重复判断。。。。话不多说,附上有详细注释的代码。

代码

#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9+10;//初始距离
const int NUM = 1e4+10;//据点数量最大值 
bool cmp(int i, int j){ //比较函数,用于后面的sort中 
	return i<j;
}
struct edge{ //表示边 
	int from,to,w;//起点,终点,边权。此题数组下标就是起点,from可以不用 
	edge(int a, int b, int c){from = a;to = b;w = c;}//构造函数 
}; 
vector<edge> e[NUM];//用来存图 
int dis[NUM];//单次求dijkstra时各点到起点的最短距离 
bool done[NUM];//表示起点到此点的最短路径是否已经找到,代替算法中的丢弃操作(优先队列没有丢弃操作) 
struct s_node{ //表示点 
	int id, n_dis; //节点号,此节点到起点的距离 
	s_node(int b, int c){id = b;n_dis = c;}//构造函数 
	bool operator <(const s_node &a)const{//重载运算符,距离越大的优先级越小,用于优先队列自动把距离最小的放队首 
		return n_dis>a.n_dis;
	}
};
int n,m,k;//节点数,边数,所求节点个数 
void dijkstra(int s){
	for(int i=1;i<=n;i++){ //初始化距离无穷大,都没有找到最短距离 
		dis[i] = INF;
		done[i] = false;
	}
	dis[s] = 0;//到自己的距离为0 
	priority_queue<s_node> Q;//声明优先队列 
	Q.push(s_node(s,dis[s]));//放入起点 
	while(!Q.empty()){ //队列为空时结束算法 
		s_node u = Q.top(); //取队首元素,即离起点最近的据点 
		Q.pop(); //删除 
		if(done[u.id]) continue; //如果已经找到到其的最短路径就丢弃(解决重边) 
		done[u.id] = true;//标记已经找到
		for(int i=0; i<e[u.id].size(); i++){ //遍历该点所有出边,包括重边 
			edge y = e[u.id][i]; //取一个边,y.to表示一个邻居 
			if(done[y.to]) continue; //如果已经找到到这个邻居的路径则丢弃,不丢也没事 
			if(dis[y.to]>y.w+u.n_dis){ //当前起点到该邻居的距离大于起点到自己的距离加边权,更新
				dis[y.to] = y.w + u.n_dis;
				Q.push(s_node(y.to,dis[y.to]));//该邻居入队,并自动排序 
			}
		}
	}
}
int main(){
	int tool;
	scanf("%d%d%d",&n,&m,&k);
	vector<int> im; //存储发动机据点编号 
	vector<int> imdis; //存储每个发动机据点到自己的距离 
	for(int i=1; i<=n; i++){ //输入据点类型 
		scanf("%d",&tool);
		if(tool==1) im.push_back(i);
	}
	int usedis[im.size()+1][NUM];//存储第i个发动机据点到第j个据点的距离 
	while(m--){ //输入边 
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		e[a].push_back(edge(a,b,c));
		e[b].push_back(edge(b,a,c));
	}
	for(int i=1; i<=im.size(); i++){ //只对发动机节点使用dijkstra
		dijkstra(im[i-1]);
		for(int j=1; j<=n; j++){
			usedis[i][j] = dis[j]; //存储距离供使用 
		}
	}
	for(int i=1; i<=n; i++){
		imdis.clear(); //输出每个节点结果前要清空 
		int re = 0;
		for(int j=1; j<=im.size(); j++){
			if(usedis[j][i]!=INF) imdis.push_back(usedis[j][i]);//只放入可达点,距离为INF说明不可达 
		}
		sort(imdis.begin(),imdis.end(),cmp);//距离从小到大排序 
		for(int j=0; j<(imdis.size()<=k?imdis.size():k); j++){ //imdis.size()和k取较小值,遍历相加 
			re += imdis[j];
		}
		printf("%d\n",re); //输出结果 
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值