「POJ 3666」Making the Grade 路面修整 题解报告

「POJ 3666」Making the Grade 路面修整

个人评价(一句话描述对这个题的情感)

灰常好!( ̄▽ ̄)"

1 算法标签

dp动态规划+滚动数组优化

2 题目难度

提高/提高+

CF rating:2300

3 题面

「POJ 3666」Making the Grade 路面修整

4 分析题面

4.1 简要描述

给出数列 \(A\), 求非严格单调不上升或单调不下降, 且\(S=\sum^N_{i=1}|A_i-B_i|\) 最小的序列\(B\),输出\(S\)

4.2 模型转换

输入\(N\), 然后输入\(N\)个数,求最小的改动这些数使之成非严格递增或非严格递减即可

5 问题分析

以B为非严格单调递增为例

5.0 暴力

我们直接当考虑已经选了\(n\)个数:

  • \(n=1,A_1=B_1\)时S最小为\(|A_1-B_1|\)

  • \(n>1\),前面已经选择了n-1个数,取得了最小值,考虑怎么取第n个数

    • \(A_i≥B_{i-1}\)\(B_i=A_i\)显然最优

    • \(A_i< B_{i-1}\)

      • \(B_i=A_i\)

      • \(B_k,B_{K+1},...,B_i\)都赋值为\(A_k,A_k+1,...,A_i\)的中位数

      口胡证明:

      我们可以将\(B_k,B_{K+1},...,B_i\)标记在数轴上

      再将\(A_k,A_k+1,...,A_i\)标记上

      那么,其实S的几何含义就是每一组\(A_i\)\(B_i\)的距离之和

      我们的小学数学也学过绝对值最值问题:

      \(|x-k_1|+|x-k_2|+|x-k_3|...\)的最小值

      其实和这里的\(S\)是没有任何区别的

      所以,我们知道零点分段法可以解决这类问题

      就是取中位数(就是使每个绝对值内部为0的x答案数组的中位数)

      可以使得绝对值之和最小

    1. 如果\(x\)在两个\(k\)之间,那么无论\(x\)在哪,距离之和都是这两个\(k\)的距离

    2. 如果在这两个\(k\)之外,那么距离之和则为两个\(k\)距离加上两倍的\(x\)距近的\(k\)的距离,肯定不会优于于第一种情况

    那么我们只要尽量让\(x\)在越多的\(k\)之间即可

    那么最佳解\(x\)在图中就是\(4\),如果\(k\)的个数为偶数\(n\),则是\(k_{n/2}和K_{n/2+1}\)之间

    综上,选择中位数最佳

5.1 dp(动态规划)

通过综上分析(5.0中),我们直接暴力模拟肯定是不行的(这个复杂度直接爆掉了)

但是!

我们可以从中得到一个\(very\) \(important\)的结论:

\(B\)数列中的每个数必定都为\(A\)数列中的元素

所以,我们可以考虑用\(dp\)来解决:

阶段:到第\(i\)

状态:\(dp_{i,j}\)表示以\(B_j\)结尾的\(S_{min}\)

B数组是A的复制排序处理过后的数组

$\space \space $ \(dp[i][j]\)表示把前i个数变成不严格单调递增且第\(i\)个数变成原来第\(j\)大的数的最小代价

转移方程:\(dp_{i,j}=min(dp_{i-1,k})+|A_i-B_j|,其中1≤j≤n,1≤k≤j\)

6 实现细节

6.1 dp(动态规划)

6.1.1 滚动数组

从我们的\(dp\)方程:\(dp_{i,j}=min(dp_{i-1,k})+|A_i-B_j|,其中1≤j≤n,1≤k≤j\)

灰常容易地阔以算出空间复杂度是\(O(n^2)\)

这个。。秉承着我们能省则省的原则

看到这个开二维数组\(O(n^2)\)的空间貌似有点浪费

那怎么去优化空间呢?

又由于我们的\(dp\)方程中只用到了\(i-1\)的信息

于是我们下意识地反应:

——用滚动数组优化!

\(\space \space\)用位运算符&来记录,这样就只用了\(0/1\)来表示

重复利用,节省空间

\(\space\space\space\space\) \(i\)&\(1\)的效果和\(i\)%\(2\)的效果是一样的,但是\(i\)&\(1\)要快一点

\(\space\space\space\space\) 且这种方式比直接写\(0/1\)少了一个不断交换的过程

\(\space\space\space\space\) 窝jio得这个东西还是很·····香的

\(i->i\) & \(1\)\(i-1->(i-1)\)&\(1\)

方程就变成了这样:

\(dp[i\)&\(1][j]=min(dp[(i-1)\)&\(][k])+|A[i]-B[j]|,其中1≤j≤n,1≤k≤j\)

6.1.2 最小值

但是这个复杂度。。

看上去好像是3层循环,就是\(O(n^3)\)

\(n<=2000\) 的时候就已经\(game\space over\)了,显然不行啊

这个xiao问题应该有手就行

其实只要一边更新\(min(f[i-1][k])\)一般算当前的\(f[i][j]\)就行

(因为\(k\)只到\(j\)

6.1.3 降序

不严格单调不上升的情况也一样

更加方便的是可以把\(B\)数组从大到小排序后,做单调不下降的dp就🆗了

6.1.4 时间复杂度

二维DP,所以就是\(O(n^2)\)

7 代码实现

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+2;
int n,f[2][N],a[N],b[N],ans=0x3f3f3f3f;
bool cmp1(int x,int y){
    return x<y;
}//升序 
bool cmp2(int x,int y){
    return x>y;
}//降序 
void work(){
    for(int i=1;i<=n;i++){
        f[1][i]=abs(a[1]-b[i]);
    }//边界条件
    for(int i=2;i<=n;i++){
        int minn=f[(i-1)&1][1];
        for(int j=1;j<=n;j++){
            minn=min(minn,f[(i-1)&1][j]);//边更新边求
            f[i&1][j]=minn+abs(a[i]-b[j]);
            //滚动数组 
        }
    } 
    for(int i=1;i<=n;i++){
        ans=min(ans,f[n&1][i]);
    }//求答案
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        b[i]=a[i];//拷贝到b数组
    }
    sort(b+1,b+1+n,cmp1);//从小到大 
    work(); //dp计算
    sort(b+1,b+1+n,cmp2);//从大到小 
    work();//直接就是一样的啊

    printf("%d",ans);//输出最小
    return 0;
}

8 总结

  1. 最大的收获:就算做不出来也需要想一些可能的做法,说不定就撞对了

  2. 新鲜的知识:更优秀的滚动数组写法

  3. 相似的题目:CF #371 div.1 C Sonya and Problem Wihtout a Legend

posted @ 2022-06-06 11:25  _Youngxy  阅读(74)  评论(0)    收藏  举报