向前走莫回头❤

【bzoj 十连测】[noip2016十连测第五场]Problem C: travel(模拟)


【题解】【模拟】
【一眼dp....然后、就没有然后了。。。】
【实际上,就是个模拟,只是不好想到】
【首先,考虑特殊情况:当l=0,但s≠1时,肯定是无解的(每个格都要遍历到,如果但却不能往左走,那么根本无法做到);当l=n-1,但s≠n时,也是无解的(每个格都只能遍历一遍)】
【接着,考虑s=1,t=n的情况。在这种情况下,若l≠0,那么必须要走“回头路”,这样一来,就有至少l个区间被重复走三次(从左往右走的时候经过了一遍,因为要走“回头路”,所以在从右往左走的时候也会再经过一遍,但因为最终是向右走,那么还要再经过一遍)。】
【推广上面这种方法,即s、t两侧还有区间,这些区间都是要走两遍的,[s,t]区间里就用上面的方法找出最小的l个区间。那么可知,l越小,总值越小,因为,在s、t的两侧不论走多少“回头路”,值都不变,所以可以尽量把l分布在两侧。所以,要首先把l尽量放到s外侧,如果l<s,那么就在“回头路”全部在s外侧走;其次,如果l-s=n-s-1,那么,把剩下的走“回头路”的次数全部放到t外侧;如果这两个条件都不满足,在枚举t前,先将l-=s,每枚举到一个t,若l<n-t,那么,把剩下的走“回头路”的次数全部放到t外侧,不然先将l-=(n-t),再在[s,t]中寻找较小的l个区间】

【[s,t]区间内的区间值用小根堆维护,每次取出较小的l个区间,重复三次,这个地方可以继承前面的,由于在t不断往后移的前提下,[s,t]区间内的l只会越来越大,所以能保证单调,可以不断往后更新】
【要注意每个点只能遍历一次,所以,在处理[s,t]区间内的情况时,一个区间能够加入,必须保证区间的右边界距s大于2个点,不然,走“回头路”会导致重复经过点】
【上述过程要做两遍,因为我们在找t时,是默认s<t的,但是,t也有在s左边的情况。所以把序列翻转、取反,再做一遍。将s<t的最小值和s>t的最小值比较、输出】   

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll unsigned long long
using namespace std;
struct node{
    ll val;
    bool operator < (const node &x)const
    {
        return val>x.val;
    }
};
int n,l,s,x[200010],y[200010];
inline ll solve(int l,int s,int t,int a[])
{
    priority_queue<node>que;
    if(l<s) return (ll)(a[s]-a[1]+a[n]-a[1]);
    l-=(s-1);
    if(l==n-s-1) return (ll)(a[s]-a[1]+a[n]-a[s+1]+a[n]-a[1]);
    ll sum=a[s]-a[1],minn=(a[n]-a[1])*2,sm=0;
    int tt=0;
    for(int i=s+1;i<=n;++i)
      {
        node ss; int len=l-(n-i);
        if(i-2>s) ss.val=a[i-1]-a[i-2],que.push(ss);
        while(!que.empty()&&tt<len)
         {
            node v=que.top(); que.pop();
            sm+=v.val; tt++;
          }
        ll k=sm*2+a[n]-a[i]+sum+a[n]-a[1];
        if(k<minn) minn=k;
     }
    return minn;
}
int main()
{
//  freopen("travel.in","r",stdin);
//  freopen("travel.out","w",stdout);
    int i,j;
    scanf("%d%d%d",&n,&l,&s);
    for(i=1;i<=n;++i) scanf("%d",&x[i]),y[n-i+1]=-x[i];
    if(!l&&s!=1) {printf("-1\n"); return 0;}
    if(l==n-1&&s!=n) {printf("-1\n"); return 0;}
    ll s1=solve(l,s,n,x);
    ll s2=solve(n-1-l,n-s+1,n,y);
    printf("%lld\n",(s1>s2?s2:s1));
    return 0;
}


posted @ 2016-11-17 08:29  lris0-0  阅读(106)  评论(0编辑  收藏  举报
过去的终会化为美满的财富~o( =∩ω∩= )m