【Codeforces 1343E】Weights Distributing(最短路)

原题链接:E. Weights Distributing
原题截图:
Weights Distributing(1)
Weights Distributing(2)Weights Distributing(3)
题目大意:
给出一张有n个节点和m条边的无向连通图,再给定m的权值,要求将这m个权值分配到m条边上,使得从a到b再从b到c的距离最短,输出最短距离


解题思路:
这个问题的难点有两个:

  1. 路径要求从a到b再从b到c
  2. 要自己分配边权值

那么首先简化问题:

如果是从x到y,得出一个分配的方案使得路径距离最小

那么自然想到的是,使得从x到y所经过的边最少,然后分配最小的权值即可
如果将所有边的权值初始化为1,求最短路就可以求出这个最短距离了。

再对于两条路 a->b 和 b->c ,可以想到最终的路径图无妨两种情况:
solu1
solu2
当然,第一种情况也可以看成是当M点与B点重合的第二种情况,那么就只用讨论第二种图的情形了

处理方法:

  1. 求出A到M的最短距离,M到B的最短距离(【无向图】也就是B到M的最短距离),M到C的最短距离(【无向图】也就是C到M的最短距离
  2. 将最小的权值先分配给B到M的边(因为这些边经过了两次),再将其他的权值分配给A到M和C到M

那么,确定下这个M就可以求出最短距离了,M的确定自然遍历一下所有的节点就可以了


解题步骤:

  1. 将所有的边权值初始化为1,以A为起点求最短路得到数组disa,以B的起点求最短路得到数组disb,以C为起点求最短路得到数组disc【这里的disa[i]即为从点a到点i所经过的边的个数
  2. 对p从小到大排序后,遍历所有节点m,根据上面讲的分配权值的原则,可得最短距离
    ans=min(ans, ∑ i = 1 d i s b [ m ] p [ i ] \sum_{i=1}^{disb[m]} p[i] i=1disb[m]p[i]+ ∑ i = 1 d i s a [ m ] + d i s b [ m ] + d i s c [ m ] p [ i ] \sum_{i=1}^{disa[m]+disb[m]+disc[m]} p[i] i=1disa[m]+disb[m]+disc[m]p[i])

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<climits>
using namespace std;
const int len=2e5+10;

int t,n,m,a,b,c,u,v;
long long p[len],ans;
int head[len],nxt[2*len],to[2*len],cnt;
int disa[len],disb[len],disc[len];

void init(){
	for(int i=1;i<=n;i++){
		head[i]=0;
		disa[i]=INT_MAX;
		disb[i]=INT_MAX;
		disc[i]=INT_MAX;
	}
	for(int i=1;i<=2*m;i++){
		nxt[i]=0;
	}
	cnt=0;
	disa[a]=0;
	disb[b]=0;
	disc[c]=0;
}

void addEdge(int u,int v){
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

struct s_node{
	int id;
	int dis;
	s_node(int a,int b){
		id=a;
		dis=b;
	}
	bool operator < (const s_node &a) const {
		return dis>a.dis;
	}
};
int done[len];
void dijkstra(int s,int dis[]){
	memset(done,0,sizeof(done));
	priority_queue<s_node>node;
	node.push(s_node(s,0));
	
	while(!node.empty()){
		s_node x=node.top();
		node.pop();
		if(done[x.id]){
			continue;
		}
		done[x.id]=1;
		
		for(int i=head[x.id];i;i=nxt[i]){
			if(done[to[i]]){
				continue;
			}
			if(dis[to[i]]>dis[x.id]+1){
				dis[to[i]]=dis[x.id]+1;
				node.push(s_node(to[i],dis[to[i]]));
			}
		}
	}
}

int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d%d%d",&n,&m,&a,&b,&c);
		init();
		for(int i=1;i<=m;i++){
			scanf("%lld",&p[i]);
		}
		sort(p+1,p+1+m);
		for(int i=1;i<=m;i++){
			p[i]+=p[i-1];
		}
		
		for(int i=1;i<=m;i++){
			scanf("%d%d",&u,&v);
			addEdge(u,v);
			addEdge(v,u);
		}
		
		dijkstra(a,disa);
		dijkstra(b,disb);
		dijkstra(c,disc);
		
		ans=LLONG_MAX;
		for(int i=1;i<=n;i++){
			if(disa[i]+disb[i]+disc[i]>m){
				continue;
			}
			ans=min(ans,p[disb[i]]+p[disa[i]+disb[i]+disc[i]]);
		}
		
		printf("%lld\n",ans);
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值