Making the Grade
题目
求构造一个单调不减或者单调不增的最小花费
花费:把 a i = > b i a_i => b_i ai=>bi的花费为 ∣ a i − b i ∣ |a_i-b_i| ∣ai−bi∣.
思路
- 这是一个求构造LIS的最小花费问题,优先选择动态规划。
- 定义: d p [ i ] [ j ] dp[i][j] dp[i][j]表示考虑前 i i i个数,把第 i i i个数改为 b j b_j bj的最小花费。
- 容易推出状态转移方程为 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]的最小花费加上这次要改动的花费,即:
d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ k ] ) + ∣ a i − a j ∣ , ( a k ≤ a j ) dp[i][j] = min(dp[i-1][k])+|a_i-a_j|,(a_k \leq a_j) dp[i][j]=min(dp[i−1][k])+∣ai−aj∣,(ak≤aj)
- 因为要寻找合适的k,这显然是一个
O
(
n
3
)
O({n^3})
O(n3)的算法,因此需要进一步优化。
- 考虑到转移过程中花费值与k无关,因此可以在更新 d p [ i ] [ j ] dp[i][j] dp[i][j]的同时保存 m i n ( d p [ i − 1 ] [ k ] ) min(dp[i-1][k]) min(dp[i−1][k]),于是我们可以引入一个变量 p r e m i n premin premin表示上一行j, [ 1 , j ] [1,j] [1,j]范围内的最小值。
- 但如果这样的话,我们无法保证 ( a k ≤ a j ) (a_k \leq a_j) (ak≤aj)的成立。考虑到改变到什么值备选方案的顺序无关,因此只要在之前把备选值 s o r t sort sort,就可以保证在 [ 1 , j ] [1,j] [1,j]中的备选值都是符合要求的,即 p r e m i n premin premin符合要求
- 综上,我们优化出了一个 O ( n 2 ) O({n^2}) O(n2)的算法。
- 对于单调不增序列,把备选数组翻转一遍,再求一次即可。
对于核心代码步骤简述:
for(int i=1;i<=n;i++)
{
int pre_min=inf;
for(int j=1;j<=len;j++)
{
pre_min=min(pre_min,dp[i-1][j]);
dp[i][j] = pre_min + abs(a[i]-b[j]);
}
}
- 初始化 p r e m i n premin premin
- 更新 p r e m i n premin premin,此时它表示[1,1]中 d p [ i − 1 ] dp[i-1] dp[i−1]中的最小值,由于已经排序,可以保证修改后的 a i a_i ai一定大于等于 a i − 1 a_{i-1} ai−1,从而保证答案正确性。
- 更新 d p [ i ] [ j ] dp[i][j] dp[i][j]
- 再次更新 p r e m i n premin premin,此时它的管辖区间就是 [ 1 , 2 ] [1,2] [1,2]了,刚好可以用来更新 d p [ i ] [ 2 ] dp[i][2] dp[i][2]的值
- 重复上述步骤, m i n ( d p [ n ] ) min(dp[n]) min(dp[n])就是答案。
AC代码
时间复杂度: O ( n 2 ) O(n{^2}) O(n2)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<fstream>
#define inf 0x3f3f3f3f
#define linf 0x3f3f3f3f3f3f3f3f
#define ll long long
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e3, mod = 1e9 + 7;
int n,len;
ll dp[N][N];// 考虑前i,第i改成j的最小花费
int a[N],b[N];
ll solve()
{
memset(dp,0,sizeof dp);
for(int i=1;i<=n;i++)
{
ll pre_min=linf;
for(int j=1;j<=len;j++)
{
pre_min=min(pre_min,dp[i-1][j]);
dp[i][j] = pre_min + abs(a[i]-b[j]);
}
}
// cout << *min_element(dp[n]+1,dp[n]+1+len) << endl;
return *min_element(dp[n]+1,dp[n]+1+len);
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",a+i), b[i] = a[i];
sort(b+1,b+1+n);
len = unique(b+1,b+1+n)-b-1;
ll res=solve();
reverse(b+1,b+1+n);
res=min(res,solve());
printf("%d\n",res);
return 0;
}
据说本题数据很水,根本不用考虑单调不增的情况···
绝了。