【线性DP】 分级

题目

题目提交点: 点我进入ACwing官方提交此题

给定长度为 N 的序列 A,构造一个长度为 N 的序列 B,满足:

B 非严格单调,即 B1≤B2≤…≤BN 或 B1≥B2≥…≥BN。
最小化 S=∑Ni=1|Ai−Bi|。
只需要求出这个最小值 S。

输入格式
第一行包含一个整数 N。

接下来 N 行,每行包含一个整数 Ai。

输出格式
输出一个整数,表示最小 S 值。

数据范围
1≤N≤2000,
0≤Ai≤109
输入样例:
7
1
3
2
4
5
3
9
输出样例:
3

题目思路

为了能够构造出最小的B序列,我们可以想到,先挑出最长上升子序列,这样的话,这些最长上升子序列放到对应的地方所产生的值是为零。然后我们再去补值:
在这里插入图片描述
因为要保持上升,所以这两个位置需要这么放最好。
我们再看一个
在这里插入图片描述
这个想法还是很多缺陷,只是带领大家往这个方向想,严谨的证明方法可以采用微调法证明。
那么我们就可以推断,在满足最小化的情况下,一定存在一种构造序列B的方案,使得B中的值都在A中出现过。

  • DP分析

集合表示: 前 i 个 数 已 经 构 造 完 成 , 且 最 后 一 个 数 是 B j 前i个数已经构造完成,且最后一个数是B_j iBj(因为要枚举到选 B j B_j Bj的情况)
属性: m i n min min

那么状态划分就要以倒数第二个数来进行划分。
在这里插入图片描述
状态转移方程为: f [ i , j ] = m i n ( f [ i , j ] , f [ i − 1 , k ] + ∣ A i − B j ∣ ) f[i, j] = min(f[i,j], f[i - 1, k] + |A_i - B_j|) f[i,j]=min(f[i,j],f[i1,k]+AiBj)

但是这样就有三重循环,我们需要减少一重循环才不会超时,如何优化呢?
我们可以发现, f [ i , j − 1 ] f[i, j - 1] f[i,j1]的状态转移方程只是比上面那个方程少一个k = j而已,其他都是重复的,所以就可以用一个值来代替。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define debug(a) cout << #a << " = " << a << endl;
#define mod(x) (x) % MOD
#define ENDL "\n"
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 2000 + 10, INF = 0x3f3f3f3f;
int f[N][N], a[N], n, b[N];

int dp() {
    memcpy(b, a, sizeof a);
    sort(b + 1, b + n + 1);

    int ans = INF;
    _rep(i, 1, n) {
        int minv = INF;
        _rep(j, 1, n) {
            minv = min(minv, f[i - 1][j]);
            f[i][j] = minv + abs(a[i] - b[j]);
            if (i == n) ans = min(ans, f[i][j]);
        }
    }
    return ans;
}

int main()
{
#ifdef LOCAL
    freopen("data.in", "r", stdin);
#endif
    ios::sync_with_stdio(false); // 取消cin与stdin 的同步
    cout.tie(0), cin.tie(0);
   
    cin >> n;
    _rep(i, 1, n) cin >> a[i];
    int ans = dp();
    reverse(a + 1, a + n + 1);
    cout << min(ans, dp()) << ENDL;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值