[BZOJ1049][HAOI2006]数字序列(dp)

245 篇文章 0 订阅
211 篇文章 0 订阅

题目描述

传送门

题目大意:给出一个数列,要将其改变成单调上升序列,求最少需要改变多少个数,和在改变的数最少的情况下,每个数改变的绝对值之和的最小值。

题解

第一问,把所有的数减去标号然后求最长不下降子序列就行了
第二问,g(i)表示改好前i个的最小代价,若f(j)+1=f(i)则可以转移,求[j,i]区间内的修改代价可以暴力,枚举一个端点然后将左边的都修改成j,右边的都修改成i,这一步具体的证明可以参考http://pan.baidu.com/share/link?uk=2651016602&shareid=1490516411 orz ydc
因为数据随机,所以我们可以根据第一问求出的f进行分层,然后只在相邻的两个层内暴力,这样时间远远到达不了 O(n3)
并且需要注意的是,在序列的前后各插入一个数,这样好写一些

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define LL long long
#define N 35005

int n,LSH,ans1;
int a[N],b[N],lsh[N],f[N],C[N],p[N],lef[N],rig[N];
LL inf,ans2,g[N];

void add(int loc,int val)
{
    for (int i=loc;i<=LSH;i+=i&-i)
        C[i]=max(C[i],val);
}
int query(int loc)
{
    int ans=0;
    for (int i=loc;i>=1;i-=i&-i)
        ans=max(ans,C[i]);
    return ans;
}
int cmp(int a,int b){return f[a]<f[b]||(f[a]==f[b]&&a<b);}
LL Abs(LL x){return (x>0)?x:-x;}
LL calc(int l,int r)
{
    LL now=0;
    for (int i=l+1;i<r;++i) now+=Abs(lsh[a[i]]-lsh[a[r]]);
    LL ans=now;
    for (int i=l+1;i<r;++i)
    {
        now-=Abs(lsh[a[i]]-lsh[a[r]]);
        now+=Abs(lsh[a[i]]-lsh[a[l]]);
        ans=min(ans,now);
    }
    return ans;
}
int main()
{
    scanf("%d",&n);int Min=2147483647,Max=-2147483647;
    for (int i=1;i<=n;++i)
    {
        scanf("%d",&a[i+1]);a[i+1]-=i+1;
        Min=min(Min,a[i+1]);Max=max(Max,a[i+1]);
    }a[1]=Min-1,a[n+2]=Max+1;n+=2;
    for (int i=1;i<=n;++i) lsh[++LSH]=a[i];
    sort(lsh+1,lsh+LSH+1);LSH=unique(lsh+1,lsh+LSH+1)-lsh-1;
    for (int i=1;i<=n;++i) a[i]=lower_bound(lsh+1,lsh+LSH+1,a[i])-lsh;

    f[1]=1;add(a[1],1);p[1]=1;
    ans1=1;
    for (int i=2;i<=n;++i)
    {
        f[i]=query(a[i])+1;
        add(a[i],f[i]);p[i]=i;
    }
    printf("%d\n",n-f[n]);

    memset(g,127,sizeof(g));inf=g[0];
    g[1]=0;
    sort(p+1,p+n+1,cmp);
    for (int i=1;i<=n;++i)
        if (f[p[i]]!=f[p[i-1]]) lef[f[p[i]]]=i,rig[f[p[i-1]]]=i-1;
    rig[f[n]]=n;
    for (int i=1;i<f[n];++i)
    {
        int l=lef[i],r=lef[i+1];
        while (l<=rig[i])
        {
            if (f[l]!=inf)
            {
                while (r<=rig[i+1]&&p[l]>=p[r])
                    ++r;
                if (r>rig[i+1]) break;
                for (int j=r;j<=rig[i+1];++j)
                    if (a[p[j]]>=a[p[l]])
                        g[j]=min(g[j],g[l]+calc(p[l],p[j]));
            }
            ++l;
        }
    }
    printf("%lld\n",g[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值