POJ3666&CF714E 动态规划

题意:
给定a序列,构造非严格递增或非严格递减的b序列,使得| a[0] - b[0] | + | a[1] - b[1] | + … + | a[n-1] - b[n-1] | 最小,并求出其值。
思路:
最开始的思路是这样的:
假设构造的序列是非严格单调增的。
dp[i][j]:b序列的前i+1个数已成单调序列且最后一个元素为j时的最小成本。
转移方程:dp[i][j] = min{ dp[i-1][k] + | a[i] - j | }, 0 <= k <= j
很明显,由于j的大小,此时的时间复杂度和空间复杂度都是超过限制的。

随后,有动过离散化的念头,但想不出应如何离散化。
看了网上的题解,才明白可以把j离散化到排序后的a序列中。
那么,dp的思路就可以修改成:
dp[i][j]:b数组是排序后的a数组,从b数组中选出了前i+1个数构成了单调序列且最后一个元素为b[j]时的最小成本。
转移方程:dp[i][j] = min{ dp[i-1][k] + | a[i] - b[j] | }, 0 <= k <= j

考虑实现时,乍看需要三层循环。其实不然,由于j、k都是在第二维度的循环变量,所以可能一起操作。时间复杂度为O(n^2)。


CF714E和这题极为相似,区别在于CF的题要构造的是严格递增的序列。
我们依然可以构造一个非严格递增的序列C,使得序列B的每一个B[i] = C[i] + i,则序列B必为严格递增序列,因为若C[i] = C[i+1], 则 C{i] + i < C[i + 1] + (i + 1), 且让B序列是我们的目标序列,即得mincost。反向思考,我们可以让每个A[i] -= i后,运用上题思路求得序列C。

反思:
1、做题有时候需要大胆猜测;
2、每一个知识点都需要学扎实。
代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
using namespace std;
const int MAXN = 2000 + 10;
int A[MAXN];
int B[MAXN];
int dp[MAXN][MAXN];
int main()
{
    int n; scanf("%d", &n);
    for(int i = 0; i < n; i++)
    {
        scanf("%d", &A[i]);
    }
    memcpy(B, A, n * sizeof(int));
    sort(B, B + n);
    for(int j = 0; j < n; j++)
    {
        dp[0][j] = abs(A[0] - B[j]);
    }
    for(int i = 1; i < n; i++)
    {
        int temp = dp[i - 1][0];
        for(int j = 0; j < n; j++)
        {
            temp = min(temp, dp[i - 1][j]);
            dp[i][j] = temp + abs(A[i] - B[j]);
        }
    }
    int ans = *min_element(dp[n - 1], dp[n - 1] + n);

    reverse(B, B + n);
    for(int j = 0; j < n; j++)
    {
        dp[0][j] = abs(A[0] - B[j]);
    }
    for(int i = 1; i < n; i++)
    {
        int temp = dp[i - 1][0];
        for(int j = 0; j < n; j++)
        {
            temp = min(temp, dp[i - 1][j]);
            dp[i][j] = temp + abs(A[i] - B[j]);
        }
    }
    ans = min(ans, *min_element(dp[n - 1], dp[n - 1] + n));
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值