3 1 2
1 2 3
3
//Lyra 一开始在 2 的位置, 2->1->3 的路径中, Lyra 一共向左走
了 1 次,花费为|2-1|+|1-3|=3。
题解:贪心
当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
#include
#include
#include
#include
#include
#include
#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,greater > 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=m) {
LL ans1=sum1+sum2+sum;
if (ans1=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