题目描述
题解
无解的情况很好判断:当l=0,s!=1 或 l=n-1,s!=n是没有合法路径的。
可以把所有的点看成若干条线段,然后求线段的覆盖次数。
当起点为1,终点为n,需要向左跳L次,那么至少有L条线段是需要被覆盖3次的,其余都被覆盖一次。贪心地求前L小的线段使之被覆盖3次就能得到最优解。
推广到起点和终点不为1的情况。假设起点为s,终点为e,并且s
<
<script type="math/tex" id="MathJax-Element-49"><</script>e,那么[s,e]这一段是和上面一种情况完全等价的。但是[1,s][e,n]这2段里的所有线段都是要被覆盖两遍的。也就是说,如果[1,s][e,n]里一共有x条线段的话,我们只需要从[s,e]里选出L-x条线段使之覆盖3遍就可以了。
e在s左边实际上是和s
<
<script type="math/tex" id="MathJax-Element-50"><</script>e等价的,因为向左跳n-L-1次可以转化为向右跳L次。
也可以通过每一条遍的贡献考虑,[1,s][e,n]的边都贡献了向左跳一次,[s,e]之间被覆盖了三次的边也贡献了向左跳一次。
这题在BZ上交并不用输出方案。如果要是输出方案的话会变得及其麻烦。所以懒惰的Po没有写。。。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
#define N 200005
#define LL long long
int n,L,s,l;
LL mid,bes,chosen,cost,ans;
LL x[N];
struct hp
{
LL val;
bool operator < (const hp &x) const
{
return val>x.val;
}
};
priority_queue <hp> q;
int main()
{
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
scanf("%d%d%d",&n,&L,&s);
for (int i=1;i<=n;++i) scanf("%I64d",&x[i]);
if (L==0&&s!=1||L==n-1&&s!=n)
{
puts("-1");
return 0;
}
if (L==0&&s==1|L==n-1&&s==n)
{
printf("%I64d\n",x[n]-x[1]);
return 0;
}
l=L;mid=x[s+1]-x[s],bes=x[n]-x[1]-mid,chosen=0;cost=mid+bes*2;ans=cost;
for (int e=s+2;e<=n;++e)
{
bes-=x[e]-x[e-1];
mid+=x[e]-x[e-1];
if (n-e+s-1>=l)
{
cost=mid+bes*2;
ans=min(ans,cost);
hp now;now.val=x[e]-x[e-1];
q.push(now);
continue;
}
hp top;top.val=0;
if (!q.empty()) top=q.top(),q.pop();
else continue;
chosen+=top.val;
cost=chosen*3+mid-chosen+bes*2;
ans=min(ans,cost);
hp now;now.val=x[e]-x[e-1];
q.push(now);
}
l=n-L-1;mid=x[s]-x[s-1],bes=x[n]-x[1]-mid,chosen=0;cost=mid+bes*2;ans=min(ans,cost);
for (int e=s-2;e>=1;--e)
{
bes-=x[e+1]-x[e];
mid+=x[e+1]-x[e];
if (e-1+n-s>=l)
{
cost=mid+bes*2;
ans=min(ans,cost);
hp now;now.val=x[e+1]-x[e];
q.push(now);
continue;
}
hp top;top.val=0;
if (!q.empty()) top=q.top(),q.pop();
else continue;
chosen+=top.val;
cost=chosen*3+mid-chosen+bes*2;
ans=min(ans,cost);
hp now;now.val=x[e+1]-x[e];
q.push(now);
}
printf("%I64d\n",ans);
}