Codeforces Round #712 (Div. 2) E. Travelling Salesman Problem

题意

给出n个点,每个点有a 和 c两个属性,需要从一号点出发每个点恰好走一次,最后回到1号点。从第i号点到第j号点之间的花费为max(ci,aj - ai).
n <= 1e5
求最小花费

思路

从一号点经过每个点走了一圈,无论从圈上的哪个点走都是走这个圈最终的最小花费都一样。所以可以从任意点走。
因为从每个点的走的最小费用都为ci ,在不考虑ci的情况下从i到j的费用为max(0,a[j] - a[i] - ci);
提出所有的ci,根据这个式子可以发现到比自己小的ak的花费为0,到比自己大的ak 需要满足ak - a - c <= 0 的花费为0。更大的点花费为ak - a - c,在不考虑c的情况下(每个点都需要加上自己的c)且之间的花费大于0,经过ak1 > a的点到 ak2 > ak1 的点花费小于等于直接到达ak2. 因为最大花费都为ak2 - a,经过中间点有可能走其他花费为0的点
为了不考虑既有走上,又有走下的情况,我们可以从最小的ai出发走到最大的ai。因为如果有点没有经过点,从最大的ai返回的代价为0。
即对所有的点按照ai排序,答案就变为从1号点到 n号点的最短路(排序后)
一般建图需要给每个点建n - 1条边。根据这个题的性质只需要对每个点最多建3条边。一条边为 i 到 i - 1距离为0,(都向下走保证到所有小的点的距离都为0)一条边为i 到最大的满足(二分)ak - a - c <= 0的点距离为0(同理),最后一条边为走到第一个距离非0的点,距离为ak - a - c(黑体字的性质)。
(以后对于建很多的边时考虑一下传递性)

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define int long long
#define x first
#define y second
typedef pair<int ,int > PII;
const int N = 1e5 + 10,M = 3e5 + 10,INF = 1e18;
int h[N],e[M],ne[M],w[M],idx;
int d[N];
bool st[N];
int n;
PII a[N];
void add(int a,int b,int c){
	e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
void dijkstra(){
	priority_queue<PII,vector<PII>,greater<PII > > hp;
	memset(d,0x3f,sizeof d);
	d[1] = 0;
	hp.push({0,1});
	while(hp.size()){
		auto t = hp.top();
		hp.pop();
		int ver = t.y,dist = t.x;
		if(st[ver]) continue;
		st[ver] = true;
		for(int i = h[ver];i != -1;i = ne[i]){
			int j = e[i];
			if(d[j] > dist + w[i]){
				d[j] = dist + w[i];
				hp.push({d[j],j});
			}
		}
	}
}
signed main(){
	memset(h,-1,sizeof h);
	scanf("%lld",&n);
	int ans = 0;
	for(int i = 1;i <= n;i++){
		scanf("%lld%lld",&a[i].x,&a[i].y);
		ans += a[i].y;
	}	
	sort(a + 1,a + 1 + n);
	a[n + 1] = {INF,INF};
	for(int i = 1;i <= n;i++){
		if(i > 1) add(i,i - 1,0);
		PII v = {a[i].x + a[i].y,INF};
		int t = upper_bound(a + 1,a + 1 + n,v) - a;
//		cout << a[t].x<< ' ' <<  a[i].x+ a[i].y<<endl;
		add(i,t - 1,0);
		if(t <= n) add(i,t,a[t].x - a[i].x - a[i].y);
	}
	dijkstra();
	cout << d[n] + ans << endl;
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值