bzoj1049 [HAOI2006]数字序列 ( LIS + 区间DP)

53 篇文章 0 订阅
51 篇文章 1 订阅

bzoj1049 [HAOI2006]数字序列

原题地址http://www.lydsy.com:808/JudgeOnline/problem.php?id=1049

题意:
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。
但是不希望改变过多的数,也不希望改变的幅度太大。
求:
1.最少需要改变多少个数
2.在改变的数最少的情况下,每个数改变的绝对值之和的最小值。

数据范围
n<=35000,保证所有数列是随机的

题解:
ydc神犇的题解

神题,神结论。
O(n^3)水过随机数据。

首先对于第一问,求最少需要改变多少个数:
补集转化的思想,即求 n-(最多有多少个数不改变),
就是求最长严格上升子序列长度,用二分+单调栈即可。
(但是这里有一个转化,就是 数字-标号,转为求最长不降子序列,在第二问中有用)

对于第二问:
之前在第一问中,每个数都 数字-标号,转为求最长不降子序列了,之后得到一个f[i]数组
f[i]表示,以i结尾的最长不降子序列长度。
设 g[i]=以i结尾的最长不降子序列,从1到i,要全部转为不降的最小代价。

那么怎么求g[i]呢?

考虑最原始的求解最长不降子序列的方法。
是一个n^2的DP,f[i]由前面最大的f[j]+1满足 a[j]<=a[i]转移过来。
就是说,取i的前一个保留位置是j,( i,j )区间的全部都要改变使[i,j]不降。

于是得到一个转移方程:
令w[i,j]表示保留i,j,使区间[i,j]合法的最小代价。
g[i]=min( g[j]+w[j,i] ) ( f[i]==f[j]+1 且 a[i]>=a[j] )

w[i,j]怎么算?
有一个非常重要的结论:
对于[i+1,j]区间,必然存在一个断点m,使得如果让 i+1到m-1都变成 i,m到j都变成 j ,是保留i,j,使区间[i,j]合法的最小代价。
证明见ydc神犇
这里写图片描述
于是w[i,j]就可以枚举断点来求。

总复杂度 O(n^3) ,本来是会T的,
但是对于找到 f[i]==f[j]+1 且 a[i]>=a[j] 时,可以把 f[i]=x预先存下来,因为数列是随机的,可以水过。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#define LL long long
using namespace std;
const int N=36000;
const int inf=0x3f3f3f3f;
int n,stack[N],top=0,a[N],f[N];
LL g[N],s1[N],s2[N];
vector<int> V[N];
int find(int x)
{
    int lf=1; int rg=top;
    while(lf+1<rg)
    {
        int mid=(lf+rg)>>1;
        if(stack[mid]<=x) lf=mid;
        else rg=mid;
    }
    if(stack[lf]>x) return lf;
    else return rg;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {scanf("%d",&a[i]); a[i]=a[i]-i;}
    for(int i=1;i<=n;i++)
    {
        if(top==0||stack[top]<=a[i]) {stack[++top]=a[i];f[i]=top;}
        else if(stack[1]>a[i]) {stack[1]=a[i]; f[i]=1;}
        else {f[i]=find(a[i]);stack[f[i]]=a[i];}
        V[f[i]].push_back(i);
    }
    printf("%d\n",n-top);
    for(int i=0;i<=n+1;i++) g[i]=1LL<<60;
    V[0].push_back(0); a[0]=-inf; g[0]=0; 
    V[top+1].push_back(n+1); a[n+1]=inf; f[n+1]=top+1;
    for(int i=1;i<=n+1;i++)
    {
        int sz=V[f[i]-1].size();
        for(int j=0;j<sz;j++)
        {
            int x=V[f[i]-1][j];
            if(x>i) break;
            if(a[x]>a[i]) continue;
            s1[x]=s2[x]=0;
            for(int k=x+1;k<=i;k++)
            {
                s1[k]=abs(a[x]-a[k]); 
                s2[k]=abs(a[i]-a[k]);
            }
            for(int k=x+1;k<=i;k++)
            {
                s1[k]=s1[k-1]+s1[k];
                s2[k]=s2[k-1]+s2[k];
            }
            for(int k=x+1;k<=i;k++) 
            g[i]=min(g[i],g[x]+s1[k-1]-s1[x]+s2[i]-s2[k-1]);
        }
    }
    printf("%I64d\n",g[n+1]);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值