从一道简单的dp题中学到的...
今天想学点动态规划的知识,于是就看了杭电的课件,数塔问题啊,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题中学到的...的更多相关文章
- 一道简单的dp题 --- Greenhouse Effect CodeForces - 269B
题目链接: https://vjudge.net/problem/36696/origin 题目大意: 要求从1到m升序排列,点可以随意移动,问最少需要移动多少次, 思路: 动态规划 可以推出转移方程 ...
- QDUOJ 一道简单的数据结构题 栈的使用(括号配对)
一道简单的数据结构题 发布时间: 2017年6月3日 18:46 最后更新: 2017年6月3日 18:51 时间限制: 1000ms 内存限制: 128M 描述 如果插入“+”和“1”到 ...
- 回顾一些较简单的dp题
1.导弹拦截 (+贪心) 两问:一个导弹拦截系统最多能拦多少导弹 要拦截所有导弹至少需要多少拦截系统 第一问感觉是一个比较巧妙的方法: 维护一个单调递减的序列 length[] 记录的是拦截导弹的高 ...
- [线性DP][codeforces-1110D.Jongmah]一道花里胡哨的DP题
题目来源: Codeforces - 1110D 题意:你有n张牌(1,2,3,...,m)你要尽可能多的打出[x,x+1,x+2] 或者[x,x,x]的牌型,问最多能打出多少种牌 思路: 1.三组[ ...
- 刷题向》一道简单的思路题BZOJ1800(EASY+)
这道题其实并不难,主要原因是数据范围很小,当然数据如果大来也可以优化,但重点是在做的时候用的思路很通用, 所以本题是一道思想题(当然思想也不难) 标题里的“+”体现在一些边界处理中. 直接甩题目 De ...
- 一道简单树形dp
题意:给定一棵树,从中选出一些节点,使得不成父子关系的节点对数最多.问这个最大值是多少. 思路:首先既然是给定一颗树,先要选择合适的数据结构,来保存这颗树.由于这颗树只关心根节点在哪里,所以只需要用一 ...
- [DP题]采药
1775:采药 总时间限制:1000ms内存限制:65536kB 描述 辰辰是个很有潜能.天资聪颖的孩子,他的梦想是称为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给 ...
- [DP题]最长上升子序列
最长上升子序列 总时间限制:2000ms 内存限制:65536kB 描述 一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的.对于给定的一个序列( ...
- [DP题]吃糖果
1944:吃糖果 总时间限制:1000ms内存限制:65536kB 描述 名名的妈妈从外地出差回来,带了一盒好吃又精美的巧克力给名名(盒内共有 N 块巧克力,20 > N >0).妈妈告诉 ...
随机推荐
- mysql 对应 binlog 查看
什么是 binlog binlog,即二进制日志,它记录了数据库上的所有改变. 改变数据库的SQL语句执行结束时,将在binlog的末尾写入一条记录,同时通知语句解析器,语句执行完毕. binlog格 ...
- CAAnimation保持动画结束时的效果
配置动画时,加上一下两句 animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards;
- UIApplication概述
1.通过类方法sharedApplication可以获得唯一实例 2.可以打开mail或者email,通过openUrl方法. 3.指定UIApplicationDelegate可以跟踪各种应用状态. ...
- 在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果
在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果 目录 概述 一般来说, 法线贴图是用高模的法线图, 低模的纹理图, 来生成较好的渲染效果. 而法线图通常是通过图像处理软件来生成的, 这里 ...
- 2015/12/12 考了PAT,又回来玩Python了。
上次写飞机大战的坑还没填完,然后就有好长时间没有更新博客了.可能大家在疑惑我在干什么... 其实不是有意暂停博客更新的,十一月学校里有两个考试要准备,然后有好多实验要做.平时的课余时间本来就不是很多了 ...
- (一)在Lingo中使用集合
1. 在Lingo中使用集合 4.1 集合的基本用法和lingo模型的基本要素 Lingo虽然使用方便,但是如果要解决几万个,几十万个变量的优化问题时,我们总不能一个一个地列出x1,x2,…,x ...
- 【测试笔记】Redis学习笔记(十二)性能测试
http://blog.csdn.net/yangcs2009/article/details/50781530 Redis测试服务器一 redis_version:2.8.4 www@iZ23s8a ...
- 《设计模式》-原则三:依赖倒置原则(DIP)
这几天晚上回来都去玩了!没有坚持学习.真的好惭愧! 非常自责 后面一定要坚持 一气呵成 争取每天学一点,把这个学完. 今天主要是看了一下 设计模式中的 原则三: 依赖倒置原则(DIP) 官方是这样 ...
- FPGA基础知识8(FPGA静态时序分析)
任何学FPGA的人都跑不掉的一个问题就是进行静态时序分析.静态时序分析的公式,老实说很晦涩,而且总能看到不同的版本,内容又不那么一致,为了彻底解决这个问题,我研究了一天,终于找到了一种很简单的解读办法 ...
- 【Foreign】tty的方程math [数学]
tty的方程math Time Limit: 50 Sec Memory Limit: 128 MB Description 给定n.m.k.p. a+b+c=n, a^2+b^2+c^2=m, a ...