BZOJ 1049: [HAOI2006]数字序列

Description

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

Input

  第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。n<=35000,保证所有数列是随机的

Output

  第一行一个整数表示最少需要改变多少个数。 第二行一个整数,表示在改变的数最少的情况下,每个数改变
的绝对值之和的最小值。

Sample Input

4

5 2 3 5

Sample Output

1

4

分析

对于第一问,我们正着做比较麻烦,所以考虑补集,那么问题就转化为了使得不用改变的点最多。
设a为原数组,对于 i,j,i<j ,使得 i,j 不被修改的条件是: ajaiji ,当满足该条件时,我们就可以通过修改中间 ji1 个数来满足整个序列的严格递增,由此,我们很容易就可以得到dp方程:

f[i]=maxi1j=1(f[j]+1(aiaj>=ij))

我们对 aiajij 进行移项可得: aii>ajj ,我们定义 bi=aii ,那么这个问题就转换成了求b的最长不下降子序列。
对于第二问,从第一问中我们可以得到,如果 j 能转移到i的话必然有 f[i]=f[j]+1 ,我们设 g[i] 表示 1 i 的答案, w[i,j] 表示把区间 [i,j] 合法化所需要的带价,我们不难得到:
g[i]=minf[j]+1=f[i](g[j]+w[j+1,i])

让 a 数组单调递增,等价于让 b 数组单调不降,考虑让 b 数组单调不降的 最小花费
由于有 f[j]+1=f[i] ,所以 j<k<i,bkbj 或者 bibk , 现在一个结 论是, w[j,i] 的方案,一定会以某个 k 为分界使得 [j,k] 均为 bj [k+1,i] 均 为 ai
使用数学归纳法。 一两个数显然成立。
假设 k 个数,以下标为横坐标,最优方案下的 b 值为纵坐标,按照那个结 论分布好了,很像是一上一下两条水平线,左边一段在下面那根,右边一段在 上面那根,现在冒出一个奇葩点 p,他在两个水平线之间
显然他的 a 值在上面的上方或下面的下方,接下来就是一堆分情况讨论的 事情而已。首先他不能做在上面点和在下面点的临界点,不然的话他往上走或 往下走总能找到更好地。也就是说,他现在要么夹在一坨在下方的点中间在他 们上方,要么在一坨在上边界的点中间却在下方
两种情况很类似,讨论第一种即可。那么他一定会导致他右边这些在下方的 点为了满足合法性不得不飞到他上面,由于这个点不在的时候是优方案,所以 我们知道这右下方几个点往上跑 1 格会使变大。
既然这样的话,如果这个讨厌的点往下走会使花费变少,他肯定要往下走。 否则的话,就说明他往下走 x 格要增加 x 的花费。但是人多力量打,他走一格 才一的花费,敌不过后面一坨点
其实这样算的话也是不全面的,因为后面这些点有可能随他乱动代价都一 样,这时候大家都往上跑走到最最上面,花费还是变少了
于是皆大欢喜

代码

#include <bits/stdc++.h>

#define N 35005
#define ll long long

struct NOTE
{
    int to,next;
}e[N];

int cnt;
int next[N];

int n;

int L;
int mxDp[N];

int a[N];
int f[N];
ll g[N];
ll s1[N], s2[N];

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

int find(int x)
{
    int l = 1, r = L, res = 0;
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        if (mxDp[mid] <= x)
            res = mid, l = mid + 1;
        else r = mid - 1;
    } 
    return res;
}

void dp()
{
    memset(mxDp,127,sizeof(mxDp));
    mxDp[0] = -1 << 30;
    for (int i = 1; i <= n; i++)
    {
        int tmp = find(a[i]);
        f[i] = tmp + 1;
        L = std::max(tmp + 1, L);
        mxDp[tmp + 1] = std::min(mxDp[tmp + 1], a[i]);
    }
}

void insert(int x,int y)
{
    e[++cnt] = (NOTE){y,next[x]}; next[x] = cnt;
}

void sloveQ2()
{
    for (int i = n; i >= 0; i--)
    {
        insert(f[i],i);
        g[i] = 1LL << 60;
    }
    g[0] = 0; a[0] = -1 << 30;
    for (int x = 1; x <= n; x++)
        for (int i = next[f[x] - 1]; i; i = e[i].next)
        {
            int p = e[i].to;
            if (p > x)
                break;
            if (a[p] > a[x])
                continue;
            for (int j = p; j <= x; j++)
                s1[j] = abs(a[p] - a[j]), s2[j] = abs(a[x] - a[j]);
            for (int j = p + 1; j <= x; j++)
                s1[j] += s1[j - 1], s2[j] += s2[j - 1];
            for (int j = p; j < x; j++)
                g[x] = std::min(g[x], g[p] + s1[j] - s1[p] + s2[x] - s2[j]);
        }
}

int main()
{
    n = read();
    for (int i = 1; i <= n; i++)
        a[i] = read() - i;
    a[++n] = 1 << 30;
    dp();
    sloveQ2();
    printf("%d\n%lld\n",n - f[n], g[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值