Making the Grade (dp)

Making the Grade

题目

求构造一个单调不减或者单调不增的最小花费
花费:把 a i = > b i a_i => b_i ai=>bi的花费为 ∣ a i − b i ∣ |a_i-b_i| aibi.

思路

  • 这是一个求构造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[i1][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[i1][k])+aiaj,(akaj)

  • 因为要寻找合适的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[i1][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) (akaj)的成立。考虑到改变到什么值备选方案的顺序无关,因此只要在之前把备选值 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]);
		}
	}
  1. 初始化 p r e m i n premin premin
  2. 更新 p r e m i n premin premin,此时它表示[1,1]中 d p [ i − 1 ] dp[i-1] dp[i1]中的最小值,由于已经排序,可以保证修改后的 a i a_i ai一定大于等于 a i − 1 a_{i-1} ai1,从而保证答案正确性。
  3. 更新 d p [ i ] [ j ] dp[i][j] dp[i][j]
  4. 再次更新 p r e m i n premin premin,此时它的管辖区间就是 [ 1 , 2 ] [1,2] [1,2]了,刚好可以用来更新 d p [ i ] [ 2 ] dp[i][2] dp[i][2]的值
  5. 重复上述步骤, 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;
}

据说本题数据很水,根本不用考虑单调不增的情况···
绝了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值