滚动数组浅析
很久之前就听说过 “滚动数组”这个东东,只是做题太少,出于一定原因一直没有拿出时间来研究研究,今天系统的整理动态规划的内容时,再次邂逅,于是乎,决定对其研究一二。
那么什么是滚动数组呢?相信很多同学在做动态规划之类的一些题目时,往往会遇到MLE的问题吧?!滚动数组就是应景而生,其实就是循环利用一些空间,从而达到优化空间的目的。最简单的解释就是在DP状态转移方程中,对我们而言有需要的仅仅是之前两个或者几个状态,在递推求解时,我们便可以利用这样一个便利解题。但是,需要注意的就是,如何滚动?重点也就是如何进行"滚动"以及如何用表达式控制这个滚动。
举个例子:
在一维的情形下:
int i,d[100];
d[0] = 1;
d[1] = 1;
for (i = 2; i< 100; i++)
d[i] = d[i - 1] + d[i - 2];
printf("%d",d[99]);
在当前这个状态(循环)下,d[i]只依赖于前两个数据d[i - 1]和d[i - 2];
那么,为了节约空间我们用滚动数组的做法为:
int d[3];
d[0] = 1;
d[1] = 1;
for (i = 2; i< 100; i++)
d[i % 3] = d[(i - 1) % 3] + d[(i - 2) % 3];
printf("%d",d[99 % 3]);
注意上面的取余运算,我们成功地只保留了需要的最后3个解,数组好象在“滚动”一样,所以叫滚动数组,或者说我们在循环的利用某几段空间,所以又可以称作循环滚动数组。
同样的,在二维的情形下,同样适用于某些状态:
int i,j,d[100][100];
for(i=1;i<100;i++)
for(j=0;j<100;j++)
d[i][j]=d[i-1][j]+d[i][j-1];
上面的d[i][j]只依赖于d[i-1][j],d[i][j-1];
运用滚动数组
int i,,j,d[2][100];
for(i=1;i<100;i++)
for(j=0;j<100;j++)
d[i%2][j]=d[(i-1)%2][j]+d[i%2][j-1];
滚动数组虽好,但也存在一些不尽人意的地方,比如打印方案比较困难,当DP结束后,只有最后一个阶段的状态值,而丢失了前面的值,事实上,对于01背包问题下的,“前i个物品”这样的规划方向,只能使用逆向的打印方案,而且还不能保证它的字典序最小。
所以在需要打印方案甚至要求字典序最小方案的场合,应慎用!
具体实例见POJ 1159,带详细解析。