好久没有写蓝色的题解了,正好学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;
}//华丽的结束!