今天想学点动态规划的知识,于是就看了杭电的课件,数塔问题啊,LCS啊都是比较经典的动规了,然后随便看了看就开始做课后练习题。。。

HDOJ 1421 搬寝室

http://acm.hdu.edu.cn/showproblem.php?pid=1421

题目大意:从n(n <= 2000)个数中选出k对数(即2*k个),使它们的差的平方和最小。

例如:从8,1,10,9,9中选出2对数,要使差的平方和最小,则应该选8和9、9和10,这样最小,结果为2


因为知道是dp的题,先建个dp[][]数组,然后我就一直在想从 n 个数中选出 k 对,和从 n-1 个数中选出 k 对有什么关系...

妹夫的,想了半天没想出来有关系,一搜题解,看呆了,排序两个大字赫然映入我的眼帘...哎呀,笨死了,我这么多年饭算是白吃了...

只要先把这n个数排好序,那么新加入的数如果要使用,只能是跟倒数第二个数(第n-1个)作差(未排序之前不能保证这一点,所以无法选择),那么从n个数中选出k对数,就有两个状态来源:

1、如果没用上第n个数,那么dp[n][k] = dp[n-1][k];

2、如果用上了第n个数,那么dp[n][k] = dp[n-2][k-1] + (a[n] - a[n-1])^2

只要取二者的min()就ok了,得到状态转移方程是:dp[i][j] = min(dp[i-1][j], dp[i-2][j-1] + (a[i] - a[i-1]) * (a[i] - a[i-1]));

编程如下:

 #include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define INF 0x3f3f3f3f
#define MAXN 2005
using namespace std;
int a[MAXN];
int dp[MAXN][MAXN];
int main()
{
int n, k;
while(scanf("%d%d", &n, &k) != EOF)
{
for (int i = ; i <= n; ++i)
{
scanf("%d", &a[i]);
}
memset(dp, 0x3f, sizeof(dp));
for (int i = ; i < n + ; ++i)
{
dp[i][] = ;
} sort(a + , a + n + );
for (int i = ; i <= n; ++i)
{
for (int j = ; j <= k, j <= i / ; ++j)
{
dp[i][j] = min(dp[i-][j], dp[i-][j-] + (a[i] - a[i-]) * (a[i] - a[i-]));
}
}
printf("%d\n", dp[n][k]);
}
return ;
}

插个小插曲: 关于int最大值的定义,此处是写了 #define INF 0x3f3f3f3f....

我们知道32-int的最大值其实是0x7fffffff; 在这个程序中定义成这个值也没有问题,下面谈一下它们两个的用处区别:

1、如果定义的最大值只是单纯的用于比较,比如初始化一个min时,这样的情景把INF设为0x7fffffff;

2、很多时候我们定义一个最大值,并不是单纯的比较,而且还有松散的操作,比如这样:

在很多最短路径算法中都有这段代码

if (d[u]+w[u][v]<d[v]) d[v]=d[u]+w[u][v];
我们知道如果u,v之间没有边,那么w[u][v]=INF,如果我们的INF取0x7fffffff,那么d[u]+w[u][v]会溢出而变成负数。

也就是说对于一个无穷大,应该加上一个常数还是无穷大,而0x7fffffff + 1 就溢出了,显然不符合这一基本要求。

于是,我们发现了0x3f3f3f3f(不知道哪位大神最先使用这个数的),它的值是1061109567,也就是10^9级别的,和0x7fffffff一个数量级,它加上一个常数仍然还在32int的范围内...更加地,一个无穷大还应该满足,正无穷大 + 正无穷大 还是无穷大,恰好地,0x3f3f3f3f + 0x3f3f3f3f = 2122219134,这个数非常大但却没有超过32-bit int的表示范围,所以0x3f3f3f3f还满足了我们“无穷大加无穷大还是无穷大”的需求。

最精妙的是,我们知道在使用memset()函数给数组赋初值的时候,一般使用0,-1,true,false这四个参数,其他的可能会出现达不到想要结果的情形(比如全赋1,可能就不是1),因为memset是按字节操作的,它能够对数组清零是因为0的每个字节都是0,现在好了,如果我们将无穷大设为0x3f3f3f3f,那么奇迹就发生了,0x3f3f3f3f的每个字节都是0x3f!所以要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))。

综上,0x3f3f3f3f真的是在赋最大值时一个很不错的选择。


好的,说回到这个程序上,按照上面写的代码,提交之后,虽然能够AC,但是运行时间是800+ms,空间是15000+K,这显然不是我们想看到的结果...

那么就开始优化了,看了一下程序,显然耗时的过程不是对dp[][]求值的过程,因为输入的多组数据中,不是每组都需要求到n=2000,k=1000的,显然是在使用memset()给dp[][]赋初值的时候,由于每组数据都要初始化,所以每次都是一个MAXN^2的时间,将其改为根据n,k初始化即可,没有什么技术含量...

对于空间的优化,空间占用显然是因为我无脑的开了一个int dp[MAXN][MAXN];

我们通过研究程序发现在求解dp[i][j]的过程中,对 i 的值,实际上我们只需要保存三组就够了,因为dp[i][j]最多只会用到dp[i-1][]和dp[i-2][],对于dp[i-3][]和之前的数据是没有引用的,也就是说这些数据都是无效的了...我们只需要开一个int dp[3][MAXN/2];  (k不超过n的一半...)

这样的数组叫做“滚动数组”,相当于在原数组中每次在求解完dp[i][]之后,就把它写到dp[i-3][]那里,只用3个位置,通过这样的滚动,就完成了MAXN长度的求值,这种技巧在求解dp问题中非常常见。它对于时间上没有任何帮助,但是在空间上节省的就不是一点半点了(想想2005 * 2005 和 3 * 2005)...

采用“滚动数组”的技巧,我们顺带的把dp[][]的初始化用时也降低了...只需把原程序中的状态转移方程改为如下即可:

dp[i%3][j] = min(dp[(i-1)%3][j], dp[(i-2)%3][j-1] + (a[i] - a[i-1]) * (a[i] - a[i-1]));

改完之后的代码如下,红色部分就是改动内容:

 #include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#define INF 0x3f3f3f3f
#define MAXN 2005
using namespace std;
int a[MAXN];
int dp[3][MAXN/2];
int main()
{
int n, k;
while(scanf("%d%d", &n, &k) != EOF)
{ for (int i = ; i <= n; ++i)
{
scanf("%d", &a[i]);
}
for (int i = ; i <= 2; ++i)
{
for (int j = ; j < k + ; ++j)
{
dp[i][j] = INF;
}
dp[i][] = ;
} sort(a + , a + n + );
for (int i = ; i <= n; ++i)
{
for (int j = ; j <= k; ++j)
{
dp[i%3][j] = min(dp[(i-1)%3][j], dp[(i-2)%3][j-1] + (a[i] - a[i-1]) * (a[i] - a[i-1]));
}
}
printf("%d\n", dp[n%3][k]);
}
return ;
}

这样改完之后,再次提交,时间是 90ms,空间是 240K,已经进入了此题solution排行榜的第一页,算是比较理想的结果了(对比之前 800ms, 15000K),有兴趣的读者再去对输入输出等等细节方面优化一下,冲击一下best solution吧!

从一道简单的dp题中学到的...的更多相关文章

  1. 一道简单的dp题 --- Greenhouse Effect CodeForces - 269B

    题目链接: https://vjudge.net/problem/36696/origin 题目大意: 要求从1到m升序排列,点可以随意移动,问最少需要移动多少次, 思路: 动态规划 可以推出转移方程 ...

  2. QDUOJ 一道简单的数据结构题 栈的使用(括号配对)

    一道简单的数据结构题 发布时间: 2017年6月3日 18:46   最后更新: 2017年6月3日 18:51   时间限制: 1000ms   内存限制: 128M 描述 如果插入“+”和“1”到 ...

  3. 回顾一些较简单的dp题

    1.导弹拦截  (+贪心) 两问:一个导弹拦截系统最多能拦多少导弹 要拦截所有导弹至少需要多少拦截系统 第一问感觉是一个比较巧妙的方法: 维护一个单调递减的序列 length[] 记录的是拦截导弹的高 ...

  4. [线性DP][codeforces-1110D.Jongmah]一道花里胡哨的DP题

    题目来源: Codeforces - 1110D 题意:你有n张牌(1,2,3,...,m)你要尽可能多的打出[x,x+1,x+2] 或者[x,x,x]的牌型,问最多能打出多少种牌 思路: 1.三组[ ...

  5. 刷题向》一道简单的思路题BZOJ1800(EASY+)

    这道题其实并不难,主要原因是数据范围很小,当然数据如果大来也可以优化,但重点是在做的时候用的思路很通用, 所以本题是一道思想题(当然思想也不难) 标题里的“+”体现在一些边界处理中. 直接甩题目 De ...

  6. 一道简单树形dp

    题意:给定一棵树,从中选出一些节点,使得不成父子关系的节点对数最多.问这个最大值是多少. 思路:首先既然是给定一颗树,先要选择合适的数据结构,来保存这颗树.由于这颗树只关心根节点在哪里,所以只需要用一 ...

  7. [DP题]采药

    1775:采药 总时间限制:1000ms内存限制:65536kB 描述 辰辰是个很有潜能.天资聪颖的孩子,他的梦想是称为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给 ...

  8. [DP题]最长上升子序列

    最长上升子序列 总时间限制:2000ms 内存限制:65536kB 描述 一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的.对于给定的一个序列( ...

  9. [DP题]吃糖果

    1944:吃糖果 总时间限制:1000ms内存限制:65536kB 描述 名名的妈妈从外地出差回来,带了一盒好吃又精美的巧克力给名名(盒内共有 N 块巧克力,20 > N >0).妈妈告诉 ...

随机推荐

  1. Mac 上配置 PhpMyAdmin

    一.配置自带的 apache 服务器环境 由于 PHP apache 环境在 Mac OS上是自带的,所以不需要另处下安装包,只需要简单配置一下即可.首先打开终端输入命令: sudo vim /etc ...

  2. UVA12167 Proving Equivalences

    UVA12167 Proving Equivalences 题意翻译 题目描述 在数学中,我们常常需要完成若干命题的等价性证明. 例如:有4个命题a,b,c,d,要证明他们是等价的,我们需要证明a&l ...

  3. SpringMVC之@RequestParam @RequestBody @RequestHeader 等详解

    转自:http://blog.csdn.net/kobejayandy/article/details/12690161?reload 简介: handler method 参数绑定常用的注解,我们根 ...

  4. Table of Contents - Python 2.x

    异常 异常类的继承关系 日志 标准日志模块 Hello World Logging Configuration

  5. nodejs出现events.js:72中抛出错误 Error: listen EADDRINUSE

    <pre>events.js:72 throw er; // Unhandled 'error' event ^ Error: listen EADDRINUSE at errnoExce ...

  6. kdissert:linux下的自由脑图软件

    ----------------------------------------------------作者: 吉庆   email: jiqingwu@gmail.commainpage: http ...

  7. Eclipse升级后导入插件的方法

    昨天把eclipse 从 indigo(3.7) 升级到了 juno(4.2). 启动 eclipse 发现从前装的 android 和 maven 插件都不见了. $ ls .eclipse/ or ...

  8. Eltwise层解析

    Concat层虽然利用到了上下文的语义信息,但仅仅是将其拼接起来,之所以能起到效果,在于它在不增加算法复杂度的情形下增加了channel数目.那有没有直接关联上下文的语义信息呢?答案是Eltwise层 ...

  9. 2019年湖南多校第一场||2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018)

    第一场多校就打的这么惨,只能说自己太菜了,还需继续努力啊- 题目链接: GYM链接:https://codeforces.com/gym/101933 CSU链接:http://acm.csu.edu ...

  10. Django框架下的小人物--Cookie

    1. 什么是Cookie,它的用途是什么? Cookies是一些存储在用户电脑上的小文件.它是被设计用来保存一些站点的用户数据,这样能够让服务器为这样的用户定制内容,后者页面代码能够获取到Cookie ...