Contest1790 - 2019年第二阶段我要变强个人训练赛第十二场 问题 I: 连边 建源点+求最短路的总路径

题目描述

给出一幅由 n 个点 m 条边构成的无向带权图。其中有些点是黑点,另外点是白点。
现在每个白点都要与他距离最近的黑点通过最短路连接(如果有很多个,可以选取其中任意一个),我们想要使得选出的边花费的代价最小。请问这个最小代价是多少?
注意:最后选出的边须保证每个白点到黑点的距离仍然是最短距离。

 

输入

第一行两个整数n,m
第二行n个整数,0表示白点,1表示黑点
接下来m行,每行三个整数x,y,z,表示一条连接x和y点,权值为z的边。

 

输出

如果无解,输出impossible;否则,输出最小代价

 

样例输入

复制样例数据

5 7	
0 1 0 1 0
1 2 11
1 3 1
1 5 17
2 3 1
3 5 18
4 5 3
2 4 5

样例输出

5

 

提示

对于30%的数据,1≤n≤10,1≤m≤20。
对于100%的数据,1≤n≤105,1≤m≤2×105,1≤z≤109。

题解:因为要满足白色到黑色的最短距离,我们倒过来想,所以我们建立一个源点,源点到每个黑色的距离为0,跑一边最短路,把是最短路的边加起来即可,注意(dis[to] == dis[now.id] + e[i].d && e[i].d < pre[to]),即为更新这个节点的路径选择最短的,既满足最小花费。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
struct edge {
	int to, nex, d;
}e[N * 4];
using namespace std;
int n, m;
int head[N], len, vis[N], pre[N];
int book[N];
ll dis[N];
ll ans;
void init() {
	for(int i = 0; i <= n + 1; i++) {
		head[i] = -1;
		dis[i] = 1e18;
		pre[i] = 0;
	}
}
void Add_edge(int x, int y, int z) {
	e[len].to = y;
	e[len].d = z;
	e[len].nex = head[x];
	head[x] = len++;
}
struct node {
	int id;
	ll d;
	node() {}
	node(int id_, ll d_) {
		id = id_;
		d = d_;
	}
	bool operator <(const node &b)const {
		return d > b.d;
	}
};
void DIJ() {
	priority_queue<node> q;
	node now;
	int to;
	q.push(node(0, 0));
	dis[0] = 0;
	while(!q.empty()) {
		now = q.top(); q.pop();
		if(vis[now.id]) continue;
		vis[now.id] = 1;
	//	cout<<now.id<<" : "<<endl;
		for(int i = head[now.id]; i != -1; i = e[i].nex) {
			to = e[i].to;
	//		cout<<to<<endl;
			if(dis[to] > dis[now.id] + e[i].d || (dis[to] == dis[now.id] + e[i].d && e[i].d < pre[to])) {
				dis[to] = dis[now.id] + e[i].d;
				ans = ans - pre[to] + e[i].d;
				pre[to] = e[i].d;
				q.push(node(to, dis[to])); 
			}
		} 
	}
	int flag = 0;
	for(int i = 1; i <= n; i++)
		if(book[i] == 0 && dis[i] == 1e18)
			flag = 1;
	if(flag) printf("impossible\n");
	else printf("%lld\n", ans); 
}
int main() {
	int x, y, z;
	scanf("%d %d", &n, &m);
	init();
	for(int i = 1; i <= n; i++) {
		scanf("%d", &book[i]);
		if(book[i] == 1) {
			Add_edge(0, i, 0);
			Add_edge(i, 0, 0);
		}
	}
	for(int i = 1; i <= m; i++) {
		scanf("%d %d %d", &x, &y, &z);
		Add_edge(x, y, z);
		Add_edge(y, x, z);
	}
	DIJ();
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值