bzoj 1049: [HAOI2006]数字序列

Description

 现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。

但是不希望改变过多的数,也不希望改变的幅度太大。

solution

正解:DP
这题相当诡异.
首先是一个套路,转化严格递增为不降,我们只需要把序列中的每一个元素都变为 \(a[i]-i\),设新数组为 \(b[i]\).
我们要改变的尽量少,就要使得不改变的尽量多,设 \(f[i]\) 表示前\(i\)个已经改变好,不改变数的最小值.
原数组 \(a[i]\) 的话,转移为 \(f[i]=Max(f[j]+1)\),需要满足 \(a[i]-a[j]>=i-j\).
对于新数组,我们只需要满足不降即可,所以 \(b[j]<=b[i]\),那么第一问我们可以转化为最长不降子序列,\(O(nlogn)\) 可以求出

对于第二问,我们设 \(g[i]\) 表示前\(i\)已经处理好的最小费用,\(g[i]=g[j]+w[j+1][i]\)\(j\) 满足 \(f[i]=f[j]+1\),我们可以挂链转移
我们需要猜一个结论:\(w[j][i]\) 的最优情况一定是存在一个 \(k\) \(i<k<j\) 使得 \(j-k\) 全部变为 \(b[j]\)\(i-k\)全部变为 \(b[i]\).
可以感性证明:前面的要选的位置尽量低,因为如果前面的选的很高,那么后面的一堆都要变化这么多,显然不会更优.
我们枚举断点转移即可,复杂度 \(O(n^3)\) 但是随机数据下跑得过,十分玄学

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=35005;
const ll inf=2e16;
ll a[N];int n,f[N];ll h[N];ll g[N],s1[N],s2[N];
il int midit(RG int l,RG int r,ll x){
   RG int mid,ret=0;
   while(l<=r){
      mid=(l+r)>>1;
      if(h[mid]<=x)ret=mid,l=mid+1;
      else r=mid-1;
   }
   return ret;
}
void solve1(){
   int t;
   a[++n]=1<<30;
   for(int i=1;i<=n;i++)h[i]=2e9+10;
   h[1]=a[1];f[1]=1;
   for(int i=2;i<=n;i++){
      t=midit(1,n,a[i]);
      f[i]=t+1;
      h[f[i]]=Min(h[f[i]],a[i]);
   }
   printf("%d\n",n-f[n]);
}
int num=0,head[N],to[N],nxt[N];
il void link(int x,int y){nxt[++num]=head[x];to[num]=y;head[x]=num;}
void solve2(){
   for(int i=n;i>=0;i--)link(f[i],i),g[i]=inf;
   g[0]=0;a[0]=-a[n];
   for(int i=1;i<=n;i++){
      for(int l=head[f[i]-1];l;l=nxt[l]){
         int j=to[l];if(j>i)break;
         if(a[j]>a[i])continue;
         for(int k=j;k<=i;k++){
            s1[k]=abs(a[j]-a[k]);
            s2[k]=abs(a[i]-a[k]);
         }
         for(int k=j+1;k<=i;k++)
            s1[k]+=s1[k-1],s2[k]+=s2[k-1];
         for(int k=j;k<i;k++)
            g[i]=Min(g[i],g[j]+s2[i]-s2[k]+s1[k]-s1[j]);
      }
   }
   printf("%lld\n",g[n]);
}
void work()
{
   scanf("%d",&n);
   for(int i=1;i<=n;i++)scanf("%lld",&a[i]),a[i]-=i;
   solve1();solve2();
}

int main()
{
	work();
	return 0;
}

posted @ 2017-10-29 22:13  Hxymmm  阅读(154)  评论(0编辑  收藏  举报