ENFP-T型人格患者|

_Youngxy

园龄:3年7个月粉丝:5关注:28

「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=i=1N|AiBi| 最小的序列B,输出S

4.2 模型转换

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

5 问题分析

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

5.0 暴力

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

  • n=1A1=B1时S最小为|A1B1|

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

    • AiBi1Bi=Ai显然最优

    • Ai<Bi1

      • Bi=Ai

      • Bk,BK+1,...,Bi都赋值为Ak,Ak+1,...,Ai的中位数

      口胡证明:

      我们可以将Bk,BK+1,...,Bi标记在数轴上

      再将Ak,Ak+1,...,Ai标记上

      那么,其实S的几何含义就是每一组AiBi的距离之和

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

      |xk1|+|xk2|+|xk3|...的最小值

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

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

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

      可以使得绝对值之和最小

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

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

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

    那么最佳解x在图中就是4,如果k的个数为偶数n,则是kn/2Kn/2+1之间

    综上,选择中位数最佳

5.1 dp(动态规划)

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

但是!

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

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

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

阶段:到第i

状态:dpi,j表示以Bj结尾的Smin

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

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

转移方程:dpi,j=min(dpi1,k)+|AiBj|,1jn,1kj

6 实现细节

6.1 dp(动态规划)

6.1.1 滚动数组

从我们的dp方程:dpi,j=min(dpi1,k)+|AiBj|,1jn,1kj

灰常容易地阔以算出空间复杂度是O(n2)

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

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

那怎么去优化空间呢?

又由于我们的dp方程中只用到了i1的信息

于是我们下意识地反应:

——用滚动数组优化!

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

重复利用,节省空间

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

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

     窝jio得这个东西还是很·····香的

i>i & 1i1>(i1)&1

方程就变成了这样:

dp[i&1][j]=min(dp[(i1)&][k])+|A[i]B[j]|,1jn,1kj

6.1.2 最小值

但是这个复杂度。。

看上去好像是3层循环,就是O(n3)

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

这个xiao问题应该有手就行

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

(因为k只到j

6.1.3 降序

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

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

6.1.4 时间复杂度

二维DP,所以就是O(n2)

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

本文作者:Yvette的博客

本文链接:https://www.cnblogs.com/yvette1217/p/16347584.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _Youngxy  阅读(52)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示