模拟赛 删区间

删区间

(remove.cpp/c)
【问题描述】
给出一个长度为?的数组?,你每次需要选出一个长度大于1的区间[?, ?]并删掉它,代价
是左右端点的元素之差的绝对值|?? − ??
|,之后再将左右两个数组接起来构成一个新的数组。
你的任务是要求出删除整个数组的最小代价和。
【输入格式】
输入文件名为 remove.in。
第一行输入一个正整数?。
接下来一行输入?个整数表示数组?。
【输出格式】
输出文件名为 remove.out。
输出一行表示最小代价和。

好题。

\(n^3\)区间DP很好想。

尝试发掘一些性质,可以发现,我们选出的区间一定不会存在包含关系,那么就可以优化DP,也就是可以固定左端点为1。
\[ f(i)=min(f(i),f(j-1)+abs(a(i)-a(j))) \]
这样就可以得到70分了。

考虑用数据结构优化DP。

先把DP式子拆开:
\[ f(i)=min(f(i),f(j-1)+a(i)-a(j))-->a(i)>=a(j); \\=min(f(i),f(j-1)+a(j)-a(i))-->a(i)<a(j) \]
可以发现出现了两个变量,分别是\(f(j-1)+a(j)\)\(f(j-1)-a(j)\)

那么对于\(a(i)\),会有两种情况的\(j\),于是可以想到用两个树状数组维护上述两个变量的前缀后缀最小值就可以了就可以了。

还有一个地方需要注意,对于\(a(j)>a(i)\)的情况,我们要查询后缀最小值,那么可以在查询和插入的时候将树状数组翻转。%%%雷哥的黑科技。

总之收获很大,也意识到要多做树状数组题了。

code:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

#define int long long

using namespace std;

const int lei_zi=500017;

inline int read(){
    int sum=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0'; ch=getchar();}
    return sum*f;
}

int s[lei_zi],t[lei_zi];
int a[lei_zi],b[lei_zi];
int f[lei_zi];

int n;

int Abs(int x){
    if(x<0)return -x;
    return x;
}

void add1(int pos,int k){
    for(int i=pos;i<=n;i+=(i&-i)){
        s[i]=min(s[i],k);
    }
}

void add2(int pos,int k){
    pos = n - pos + 1;
    for(int i=pos;i<=n;i+=(i&-i)){
        t[i]=min(t[i],k);
    }
}

int query1(int pos){
    int re=3038287259199220266LL;
    for(int i=pos;i>=1;i-=(i&-i)){
        re=min(re,s[i]);
    }
    return re;
}

int query2(int pos){
    pos=n-pos+1;
    int re=3038287259199220266LL;
    for(int i=pos;i>=1;i-=(i&-i)){
        re=min(re,t[i]);
    }
    return re;
}



signed main(){
    freopen("remove.in","r",stdin);
    freopen("remove.out","w",stdout);
    
    n=read();
    for(int i=1;i<=n;i++) 
        a[i]=read(),b[i]=a[i];
    
    memset(s,127/3,sizeof s);
    memset(t,127/3,sizeof t);
    sort(b+1,b+1+n);
    
    for(int i=1;i<=n;i++)
        a[i]=lower_bound(b+1,b+1+n,a[i])-b;
    f[2]=Abs(b[a[2]]-b[a[1]]); f[3]=Abs(b[a[3]]-b[a[1]]);
    add1(a[3],f[2]-b[a[3]]) , add2(a[3],f[2]+b[a[3]]);
    for(int i=4;i<=n;i++){
        f[i]=Abs(b[a[i]]-b[a[1]]);
        int tmp1=query1(a[i]);
        int tmp2=query2(a[i]);
        f[i]=min(f[i],tmp1+b[a[i]]);
        f[i]=min(f[i],tmp2-b[a[i]]);
        add1(a[i],f[i-1]-b[a[i]]); add2(a[i],f[i-1]+b[a[i]]);
    }
    printf("%lld\n",f[n]);
    
    fclose(stdin);
    fclose(stdout);
    return 0;
}

转载于:https://www.cnblogs.com/wangxiaodai/p/9838610.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值