题目描述
题目大意:给出一个数列,要将其改变成单调上升序列,求最少需要改变多少个数,和在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
题解
第一问,把所有的数减去标号然后求最长不下降子序列就行了
第二问,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]);
}