题意:
给定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;
}