Problem C: [noip2016十连测第五场]travel (贪心)

Problem C: [noip2016十连测第五场]travel

Time Limit: 10 Sec   Memory Limit: 233 MB
Submit: 9   Solved: 3
[ Submit][ Status][ Web Board]

Description

http://www.lydsy.com/JudgeOnline/upload/201610/statements(1).pdf
给定一个长度为 n 的格子序列 x1,x2,...,xn。每一次 Lyra 可以选择向左跳到任意一个还没到过的位置,也可以
向右跳到任意一个还没到过的位置。如果现在 Lyra 在格子 i,她下一步跳向格子 j,那么这次跳跃的花费为|xi-
xj|。 注意,跳意味着格子 i 和格子 j 中间其他的格子都不会被这次跳跃影响。并且, Lyra 不应该跳出边界。
Lyra 的初始位置在格子 s。 Lyra 将会在到访过所有格子恰好一次之后,在某个位置停下来,这样就完成了任务
。Lyra 想知道如果她一共向左跳了 L 次,那么她要完成任务的最小总花费是多少,并希望你输出任意一种花费最
小的方案。显然如果 Lyra 向左走了 L 次,那一定会向右走 n-L-l 次。特殊的, 如果 Lyra 没有办法完成任务
,请输出一行-1。

Input

第一行,三个整数 n, L, s,分别表示序列的大小,向左走的次数,和初始位置。
第二行, n 个数字,表示序列 xi。
n≤2*10^5, 0≤xi≤10^9。 x1<x2<...<xn-1<xn, 1≤s≤n, 0≤L≤n-1

Output

第一行,一个数字,表示答案,注意OJ测试只要输出这一个数字

Sample Input

3 1 2
1 2 3

Sample Output

3
//Lyra 一开始在 2 的位置, 2->1->3 的路径中, Lyra 一共向左走
了 1 次,花费为|2-1|+|1-3|=3。

HINT

[ Submit][ Status]

题解:贪心

当xi=i时,数列是等差的递增数列,每个的间距相同,所以我们肯定是考虑尽量的不走回头路,但是因为必须要向左走L步,所以至少有L段要被经过三次,那么我们从起点一直向左跳,跳到不能再跳为止,如果此时还是不够的话,我们就再跳到起点的右边正好多出剩下L步的为止,然后再向左跳,最后在向右跳即可。如果连续向左跳会多,那就考虑间隔的跳最终跳到头再转弯即可。

  -1的情况非常好处理,当 l=0但是s≠1的时候,是没有合法路径的。同理 l=n-1,s≠n也是没有合法路径的。
  先考虑特殊的情况,当 s=1的时候,显然答案的下界是 xn-x1,就是从最左边按次序一直跳到最右边,不过L>0的时候就不能这么做了。答案要求最小也就是说要尽量少走回头路。假如我们在 n这个位置停下,那么中间就需要走一些回头的路类来用掉 L,我们把所有i到i+1之间的区间看成一个线段。跳的路径相当于对线段进行覆盖。显然所有的线段都必须覆盖至少1次,而至少有 L个线段至少覆盖3次。下面给出证明。 
  假如某一次是从 i向左跳,那么之前一定会从左侧跳到i,肯定会覆盖[i-1,i]的线段,然后从i向左跳走回头路,又会覆盖[i-1,i]一次,最后因为一定要走到 n,所以在走完回头路后一定还会再向右跳回来,所以至少经过三次[i-1,i]。也就至少有L条线段被覆盖至少3次。这样我们就可以贪心的选取权值最小的L条线段计算作为答案了。显然这种情况下L越小,答案越优。

但是实际情况中,起点不一定是1,终点也不一定是n.我们假设终点t再起点s的左边,那么[1,s-1][t+1,n]中的点至少都需要经过两次,如果能在这两段中用掉较多的L,那我们中间剩下的L就会少,这样在好不过。以为中间[s,t]这一段的处理就相当于是上面[1,n]的处理,中间的线段都会被覆盖三次。那么两边最多会向左走n-t+s-1步,如果n-t+s-1>=L,那么中间的不需要向左走,直接一步一步的跳就好了。如果不够L步的话就考虑从中选取差值小的线段让其长度*3即可。然后根据对称原则,在计算终点在起点左边的情况,这时候其实就是向右跳n-L-1步,向上面一样处理即可。

然后我们就可以通过枚举终点,来更新答案。对于上面中起点相对位置的两种情况,我们单独看。每次就是在上一起点的基础上中间加入[i,i+1],然后[t+1,n]的长度减小[i,i+1],我们用小根堆维护,每次都尽可能利用小的。注意有可能存在无法满足答案的情况,就是堆中不够。

还需要注意的是中的段的头尾两条线段不能使用,因为无论怎么跳都没法满足,会影响两边的跳法。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#define N 200003
#define LL long long 
using namespace std;
int a[N],c[N],n,s,m,vis[N];
LL l[N],r[N];
priority_queue<int,vector<LL>,greater<LL> > q;
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d%d",&n,&m,&s);
	if (m==n-1&&s!=n||m==0&&s!=1) {
		printf("-1");
		return 0;
	}
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=2;i<=n;i++) l[i]=(LL)(a[i]-a[i-1]);
    for (int i=n-1;i>=1;i--) r[i]=(LL)(a[i+1]-a[i]);
    LL sum=0; LL sum1=0; LL sum2=0; LL ans=(LL)1e9*(LL)1e9; int ansx=0;
    LL sum3=0; LL last=0;
    for (int i=1;i<s;i++) sum1+=(LL)r[i]*2;
    for (int i=s;i<=n-1;i++) sum2+=(LL)r[i]*2;
    for (int i=s+1;i<=n;i++)
     {
     	int t=n-i+s-1;
        sum+=(LL)l[i]; sum2-=(LL)l[i]*2;
        if (t>=m) {
        	LL ans1=sum1+sum2+sum;
        	if (ans1<ans) ans=ans1,ansx=i;
		}
		else{
			if (q.empty()) break;
 			LL x=q.top(); q.pop();
			sum3+=(LL)x*2;
			LL ans1=sum1+sum2+sum+sum3;
			if (ans1<ans) ans=ans1,ansx=i;
		} 
		if (i!=s+1) q.push(l[i]);
	 }
	int m1=n-m-1;
	sum1=0; sum2=0; sum3=0; sum=0;
	for (int i=s;i<=n-1;i++) sum1+=(LL)r[i]*2;
	for (int i=1;i<=s-1;i++) sum2+=(LL)r[i]*2;
	while (!q.empty()) q.pop();
	for (int i=s-1;i>=1;i--)
	{
		int t=n-s+i-1;
		sum+=r[i]; sum2-=r[i]*2;
		if (t>=m1){
			LL ans1=sum1+sum2+sum;
			if (ans1<ans) ans=ans1,ansx=i;
		}
		else {
		    if (q.empty()) break;
			LL x=q.top(); q.pop();
			sum3+=(LL)x*2;
			LL ans1=sum1+sum2+sum+sum3;
			if (ans1<ans) ans=ans1,ansx=i;
		}
		if (i!=s-1) q.push(r[i]);
	}
	printf("%I64d\n",ans);
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值