好久没有写蓝色的题解了,正好学bfs的时候做到了这题,就写一个和楼上两位解法不一样的题解吧。(其实是教练教的)
思路
这题明显的图上bfs,于是乎,有一个非常朴素的思路:
对于每一个城市,我们给它bfs一次。
但是,这个思路明显不可以过!
为什么?看看它的数据范围: \(1<=n,m<=\)\(10^5\) ,而我们的算法的时间复杂度为 \(O(n*(n+m))\) ,明显爆掉。
所以得出结论,朴素的算法在蓝题及以上难度的题永远过不了。
那我们的正解怎么写呢?我的入手点仍然是数据范围。
三个变量中,只有 \(k\) 的数据范围是不到 \(100\) 的,于是乎,我决定从它下手。
接着我想了一个思路:设一个 \(dis\) 数组, \(dis[i][j]\) 表示货物种类为 \(j\) 的城市到编号为 \(i\) 的城市的最少花费。然后对于每个城市 \(i\) ,我们只需要将它的 $dis[i] $ 从小到大排序,再取前 \(s\) 小的值的和了。
由此将bfs转化为多起点的bfs问题了,就有点像血色先锋队这题了。
于是就只需要跑 \(k\) 次bfs了,而 \(O(k*(n+m))\) 的算法是肯定可以轻松跑过的!
代码:(有详细注释)
需要注意:由于bfs要跑多次,于是乎,我们的标记数组 \(flag\) 需要清空。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,k,s,a[100010],u,v,dis[100010][110],sum;
bool flag[100010];//记录这个城市是否被走过
vector<int> Gragh[100010];//用来存图的数组
vector<int> vis[110];//用来存一种货物类型所对应的城市编号
struct node{
int id,step;
};//bfs必要数组
void bfs(int b){//b表示货物的种类
queue<node> q;
memset(flag,0,sizeof(flag));//提到过的清空操作
for(int i=0;i<vis[b].size();i++){//循环所用货物种类为b的城市,并且将它们存入队列
node cur={vis[b][i],0};
q.push(cur);//结构体入队
flag[vis[b][i]]=1;//记录走过了
}
while(q.empty()==0){//队列不为空,防止RE
node cur=q.front();//取队首
q.pop();//弹出
for(int i=0;i<Gragh[cur.id].size();i++){//循环这个城市可以到的城市编号
int nx=Gragh[cur.id][i];//记录这个城市编号
if(flag[nx]==0){//如果没有走过
flag[nx]=1;//先标记
dis[nx][b]=cur.step+1;//记录最少花费
node nxt={nx,cur.step+1};
q.push(nxt);//结构体入队
}
}
}
}
int main() {
cin>>n>>m>>k>>s;
for(int i=1;i<=n;i++){
cin>>a[i];
vis[a[i]].push_back(i);//存货物类型a[i]所对应的城市编号
}
for(int i=1;i<=m;i++){
cin>>u>>v;
Gragh[u].push_back(v);
Gragh[v].push_back(u);//无向边的存储
}
for(int i=1;i<=k;i++){
bfs(i);//对于每一种货物类型,bfs一次
}
for(int i=1;i<=n;i++){
sum=0;
sort(dis[i]+1,dis[i]+k+1);//排序
for(int j=1;j<=s;j++){
sum+=dis[i][j];//把费用最低的前k个费用算和
}
cout<<sum<<' ';//输出和
}
return 0;
}//华丽的结束!