#dp、树状数组#JZOJ 3859 孤独一生

题目

\(n\)座山(给定高度和\(n\))分成两个集合(按照原次序排列),然后在两个集合前加入海拔为0的平地
现在YC——一名julao,会两次从平地开始沿竖直方向跳到下一座山(别问我怎么做到的),直至跳完该集合的所有山,
问两次重力做功和克服重力做功的最小值,你可以把他的重力视为\(1N\)(两个鸡蛋用手托起的力),可以一次跳完


分析

\(dp[i]\)表示最后一个区间为\([i\sim n]\)的最小值(不计算最后一个区间内部答案)
那么\(dp[i]=min\{dp[j]+s[i-1]-s[j]+|h[i]-h[j-1]|\}\)
枚举不同的集合\(j\),那么\(i\)就接到\(j\)的前一个(\(s\)是前缀高度绝对值)
考虑不变量只有\(s[i-1]\),绝对值得拆开分别求,一般用数据结构维护,这里用的是树状数组
注意要离散高度,对于某个\(j\)就把\(dp[j]-s[j]\pm h[j-1]\)扔进树状数组里,但是注意要以\(h[j-1]\)的实际下标维护


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int N=500011;
typedef long long lll;
int n,h[N],a[N],m; lll s[N],dp[N],c[N],ans;
inline signed abs(int x){return x<0?-x:x;}
inline lll min(lll a,lll b){return a<b?a:b;}
struct Tree_Array{
    lll c[N];
    inline void update(int x,lll y){
        for (;x<=m;x+=-x&x) c[x]=min(c[x],y);
    }
    inline lll query(int x){
        rr lll ans=1e15;
        for (;x;x-=-x&x) ans=min(ans,c[x]);
        return ans;
    }
}cpr,csu;
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans;
}
signed main(){
    n=iut();
    for (rr int i=1;i<=n;++i) a[i]=h[i]=iut(),s[i]=s[i-1]+abs(h[i]-h[i-1]);
    a[n+1]=0,sort(a+1,a+2+n),m=unique(a+1,a+2+n)-a-1,ans=1e15;
    for (rr int i=1;i<=m;++i) cpr.c[i]=csu.c[i]=ans;
    for (rr int i=1,lpr=1,lsu=m;i<=n;++i){
        rr int pr=lower_bound(a+1,a+1+m,h[i])-a,su=m-pr+1;
        dp[i]=s[i-1]+min(h[i]+cpr.query(pr),csu.query(su)-h[i]);
        if (i==1) dp[i]=h[i];
        cpr.update(lpr,dp[i]-s[i]-h[i-1]);
        csu.update(lsu,dp[i]-s[i]+h[i-1]);
        ans=min(ans,dp[i]+s[n]-s[i]),lpr=pr,lsu=su;
    }
    return !printf("%lld",ans);
}
posted @ 2020-02-09 12:22  lemondinosaur  阅读(124)  评论(0编辑  收藏  举报