题解 P2893 【[USACO08FEB]Making the Grade G】

· · 题解

DP

首先看数据范围,n^2可以过。

单调不降或者单调不升,我们很容易想到DP。

我们想要花费最小,说明尽可能让每个数动的幅度小一点。能不动就不动,如果必须要动的话,变成上一个位置上的数就可以了。这里假设上一个位置的数已经确定。

所以一个数要么是自己,要么是上一个位置上的数。而上一个数怎么来的呢?很明显,也是这样,要么不动,要么是上上个位置的数。可以一直推下去。这就口胡得出了众题解中的结论了:

存在一种最优方案,每个位置上的数,是原序列中的数。

下面由单增举例:

我们设f[i][j]表示第i个位置放j时的方案数,显然,前一个位置放的数必须比j小。

为了转移方便,我们处理一个b数组,为a数组排序后的数,f[i][j]表示第i个位置放b[j]时方案数,前一个位置为b[1]~b[j],这就满足了前一个位置比这个位置小的要求。

于是得出转移方程:

所以我们定义一个$Min[i][j]$表示f[i][1]~f[i][j]中的最小值。 则新的转移方程为: ### $f[i][j]=Min[i-1][j]+|a[i]-b[j]|

下面只贴单增的代码 (虽然也可以过Q^Q

#include<bits/stdc++.h>
using namespace std;
const int amou=2101,Inf=0x3f3f3f3f;
int a[amou],b[amou],n;
int f[amou][amou],Min[amou][amou];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    for(int i=1;i<=n;i++)
    {
        Min[i][0]=Inf;
        for(int j=1;j<=n;j++)
        {
            f[i][j]=Min[i-1][j]+abs(a[i]-b[j]);
            Min[i][j]=min(Min[i][j-1],f[i][j]);
        }
    }
    printf("%d",Min[n][n]);
    return 0;
}