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

来源:互联网 发布:seo自然排名 编辑:程序博客网 时间:2024/05/22 09:40

题目描述

传送门

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

题解

第一问,把所有的数减去标号然后求最长不下降子序列就行了
第二问,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 35005int 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 0
原创粉丝点击