题面
优化过程
思路
最短路径问题,可用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); //输出结果
}
}