Codeforces 1503C Travelling Salesman Problem(Dynamic Programming)

题意

大家都是优秀生,这点英文还是看得懂的:点此看题

题解

由于旅行路线成一个环,所以从哪里出发不重要,我们把景点按照 a i a_i ai 排序,不妨就从左边最小的出发。基础的旅行费用 c i c_i ci 是都要算上的,我们的目的是最小化额外的费用 ∑ max ⁡ ( 0 , ( a j − a i ) − c j ) \sum\max(0,(a_j-a_i)-c_j) max(0,(ajai)cj)

很明显有一部分的景点是回程(从大到小飞回起点)的时候经过的,这些景点一定没有额外费用。其它景点是来路经过的,费用取决于相邻两个的“间距”( a a a 的差)。最左端和最右端都在来路上。

我们不妨用 d p [ i ] dp[i] dp[i] 表示来路上经过 i i i 时,最小的额外花费是多少。那么假设从 i i i 往左数,直到 k k k 以内的前驱,到 i i i 的额外花费都是 0(这个 k 可以预处理出来),则转移为:
d p [ i ] = min ⁡ { min ⁡ j = k i − 1 d p [ j ]   ,   a i + ( min ⁡ j = 1 k − 1 ( d p [ j ] − a j ) ) − c i } dp[i]=\min\bigg\{\min_{j=k}^{i-1} dp[j]~,~a_i+\big(\min_{j=1}^{k-1} (dp[j]-a_j)\big)-c_i\bigg\} dp[i]=min{j=kmini1dp[j] , ai+(j=1mink1(dp[j]aj))ci}

可以分别建立两棵线段树来优化 D P \rm DP DP 。时间复杂度 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)

CODE

和上面说的不同,这个是从右到左算的。

#include<set>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
struct it{
	int a,c,id;
}p[MAXN];
LL dp[MAXN];
bool cmp(it x,it y) {
	if(x.a != y.a) return x.a < y.a;
	if(x.id == 1) return 1;
	if(y.id == 1) return 0;
	return x.c < y.c;
}
int tre1[MAXN<<2],tre2[MAXN<<2],M;
int bing1(int x,int y) {
	if(!x || !y) return x+y;
	if(dp[x] != dp[y]) return dp[x] < dp[y] ? x:y;
	return max(x,y);
}
int bing2(int x,int y) {
	if(!x || !y) return x+y;
	if(dp[x]+p[x].a != dp[y]+p[y].a) return dp[x]+p[x].a < dp[y]+p[y].a ? x:y;
	return max(x,y);
}
void maketree(int n) {M=1;while(M<n+2)M<<=1;}
void addtree(int x,int y) {
	int s = M+x;tre1[s] = tre2[s] = y;s >>= 1;
	while(s) tre1[s] = bing1(tre1[s<<1],tre1[s<<1|1]),tre2[s] = bing2(tre2[s<<1],tre2[s<<1|1]),s >>= 1;
}
int findtree1(int l,int r) {
	int as = 0; if(l > r) return as;
	int s = M+l-1,t = M+r+1;
	while(s || t) {
		if((s>>1) ^ (t>>1)) {
			if(!(s & 1)) as = bing1(as,tre1[s^1]);
			if(t & 1) as = bing1(as,tre1[t^1]);
		}else break;
		s >>= 1;t >>= 1;
	}return as;
}
int findtree2(int l,int r) {
	int as = 0; if(l > r) return as;
	int s = M+l-1,t = M+r+1;
	while(s || t) {
		if((s>>1) ^ (t>>1)) {
			if(!(s & 1)) as = bing2(as,tre2[s^1]);
			if(t & 1) as = bing2(as,tre2[t^1]);
		}else break;
		s >>= 1;t >>= 1;
	}return as;
}
int rr[MAXN];
int main() {
	n = read();
	LL ans = 0;
	for(int i = 1;i <= n;i ++) {
		p[i].a = read();p[i].c = read();
		ans += p[i].c;
		p[i].id = i;
	}
	sort(p + 1,p + 1 + n,cmp);
	maketree(n);
	for(int i = 1;i < n;i ++) {
		rr[i] = i;
		for(int j = 17;j >= 0;j --) {
			if(rr[i]+(1<<j) <= n && p[rr[i]+(1<<j)].a <= p[i].a+p[i].c) rr[i] += (1<<j);
		}
	}
	dp[n] = 0;
	addtree(n,n);
	for(int i = n-1;i > 0;i --) {
		int t1 = findtree1(i+1,rr[i]),t2 = findtree2(rr[i]+1,n);
		dp[i] = 1e18;
		if(t1) dp[i] = min(dp[i],dp[t1]);
		if(t2) dp[i] = min(dp[i],dp[t2] + p[t2].a - (p[i].a+p[i].c));
		addtree(i,i);
	}
	printf("%lld\n",ans + dp[1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值