【POJ 3666】Making the Grade【线性DP】

题意:

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

1.B非严格单调,即B1 <= B2 <= ... <= BN 或 B1 >= B2 >= ... >= BN.

2.最小化 S = (累加)| Ai - Bi |,只需要求出这个最小值S.

   1 <= N <= 2000,1 <= | Ai | <= 1e9

思路:

可以证明,我们能够只用A中的数字就可以构造出符合条件的B【虽然我不会证明,但是我觉得如果再遇到这种题,我应该也会这么猜的吧】。

既然可以用A中数字构造B,那么可以考虑先对A进行一个排序,本题单调增和单调减都需要求出,然后再比较最小值。【不过,写完之后发现只考虑单调增就可以通过本题,考虑单调减不可行】

这里只考虑单调增,因此用一个num数组将A复制下来,将num数组排升序,并且去重。由于n比较小,但是Ai很大,因此将Ai映射到数组中,进行离散化操作。

令dp[ i ][ j ]为B序列中最后一个数为num[ j ],并且前 i 个数字的cost最小,则

                   dp[ i ][ j ] = min(dp[ i-1 ][ x ]) + abs(a[ i ] - num[ j ])

由于更新dp[ i ][ j ]的时候,j始终不变,因此遍历过程中取一个最小值,进行依次更新,即可将On^3的复杂度降为On^2.

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;

int n;
int a[2500],num[2500];
int dp[2500][2500]; //dp[i][j]表示前i个数字花费最小,并且最后以num[j]结尾
//由于数字的范围很大,但是数字个数比较少,因此进行离散化,即把数字映射到区间上

int main()
{
	while(~scanf("%d",&n))
	{
		rep(i,1,n){
			scanf("%d",&a[i]);
			num[i] = a[i];
		} 
		sort(num+1,num+1+n);
		int m = unique(num+1,num+1+n)-num-1;
		memset(dp,0,sizeof dp);
		rep(i,1,n)
		{
			//由于i不变,因此可以记录最小值
			int tmp = dp[i-1][1];
			rep(j,1,m)
			{
				tmp = min(tmp,dp[i-1][j]);
				//dp[i][j] = min(dp[i-1][x])+abs(a[i]-num[j]); x <= j,保证升序
				dp[i][j] = tmp+abs(a[i]-num[j]);
			}
		}
		int ans = 0x3f3f3f3f;
		rep(i,1,m)
			ans = min(ans,dp[n][i]);
		printf("%d\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/81711007