最长上升子序列nlogn算法

以前搞算法的时候看到过这个算法,但是没有仔细钻研过,似懂非懂,这两天看了看,总算是搞明白了。

问题的背景我就不多说了,相信通过搜索引擎来到这里的都是为了寻求简单易懂的nlogn的答案。

这个算法dp的状态有点诡异:

dp[i] = 长度为i+1的上升子序列中末尾元素的最小值(没有某个长度的上升自序列的话相应位置是无穷大)

初看有点不太明白,为什么是末尾的最小值呢?为什么可以求出最长的公共子序列呢?为什么可以到nlogn呢?别急,我慢慢说。

首先,当这个算法执行完毕的时候,dp数组中的每一个都符合我们定义的状态,每个长度的上升子序列末尾最小值都有了。那么我们看看含有末尾最小值的最大的位置是什么就能找到问题的解了。

其次,我们怎么去进行状态的推算和更新?其实,这个算法已经是优化过的算法,搞的人都不清楚他要干啥了。这个算法的原始版本其实还有一维(详见PSS),如果我们知道N个元素组成的dp数组,那么如果这时候又来了一个元素怎么办?dp数组能很快更新么?必须是可以的。只要新来的元素比dp数组中的某个位置j上的元素大就可以了,那么新来的元素就可以更新dp[j]了,具体是if(dp[j-1]<new)     dp[j] = min(dp[j],new),奉上代码:

for( int i = 0; i &lt; n; i++ ){// index for arrays
for( int j = 0; j &lt; n; j++ )// index for dp
if( j == 0 || (dp[j-1] &lt; a[i] &amp;&amp; a[i] &lt; dp[j] ) )
dp[j] = a[i];
}

上面简单的代码有个奇特的性质,就是每次循环完dp是单调递增的(除了无穷大),为什么呢,假如dp[3]>dp[5],长度为5的上升子序列的最后一位最小值竟然比长度为3的末尾最小值小,那我们拿长度为5的上升自序列的后面三个数更新dp[3]不就行了?

最后还有个性质,从上面代码的if语句中可以看出,因为dp是递增的,所以a[i]最多也就更新一次。不妨用二分找出更新的位置,然后复杂度就降到nlogn,你说优美不?

贴上《挑战程序设计竞赛》,65页的代码,大家好好品味

int dp[MAX_N];

void solve{
fill( dp, dp + n, INF );
for( int i = 0; i &lt; n; i++ )
*lower_bound( dp, dp + n, a[i] ) = a[i];
printf("%d\n", lower_bound(dp,dp+n,INF) - dp );
}

什么?没看懂?再看一遍吧。。

PS:以上代码未经验证
PSS:原始算法什么的是我想出来的,为了使思维过程更清晰一点,具体有没有就不知道了
PSSS:有人知道最长不下降自序列可以用nlogn的算法解么?

继续阅读 →

ZOJ 1503 One Person “The Price is Right”

好久没有做题了..刷刷DP吧,之前都没有好好做过这方面的题,比赛一遇到就放过..没有一点思路.

这是个比较基础的,还好是我自己想出来的,没有看hints.对于每对猜测次数G和生命值L,需要得到一个最大的N,让其可以找到一个策略,能在G和L都没用完前猜出正确的数,即必胜策略. 我们平时玩这种游戏的时候都是二分..然后从经验上判断这个是最快能猜到那个数的.这里因为有生命值,所以复杂了些.我们假设这个某对GL对应的最大的N为nmax,第一步我们的必胜策略肯定是从中间找一个数k,然后判断这个数是大了还是小了还是刚好. 如果没有猜中,就从左边或者右边的范围内继续查找.假设dp[i][j]表示当猜测次数是i,生命值是j的时候能得到的最大的N.

再来看k,如果k猜大了,那么生命值就减少了1.需要从小于k的范围内再猜,在小于k的范围内所能得到的最大N是dp[i-1][j-1],在大于k的范围内的到最大N是dp[i-1][j],所以可得dp[i][j] = dp[i-1][j-1] + dp[i-1][j] + 1,这就是状态转移方程.

继续阅读 →

ZOJ 2059 The Twin Towers (DP)

ZOJ 2059 The Twin Towers

被成为经典的双塔问题。痛恨死了,我搞了好多天,我真是对dp一点都不开窍啊。。怎么办?看了大黄的思路,dp[i]表示高度差为i时候低的塔的最大值,这个状态定义的很纠结,也不是很好理解。就先拿我的代码来说吧,每输入一个水晶,就更新一下dp。。。这个时候我们需要一个额外的t数组来保存前边所有物品所产生的状态,因为只用一个dp来保存的话,当前物品所产生的状态有可能叠加。。对状态的更新产生影响。。t数组每次更新完就再赋给dp。。dp[0]表示高度相等时的高度。。。
(更多…)

继续阅读 →

ZOJ 2402 Lenny’s Lucky Lotto Lists (DP)

ZOJ 2402 Lenny’s Lucky Lotto Lists

用1-M之间的数,组成一个长度为N的序列,使得每一个数是之前那个数的二倍以上,如对于N=4,M=10:

1 2 4 8
1 2 4 9
1 2 4 10
1 2 5 10

有四种选择,题目给出的范围是N<=10,M<=2000。

设DP[i][j]表示长度为i的序列结尾为j的情况数,从小往大递推就行了。。

(更多…)

继续阅读 →

ZOJ 1524 Supermarket (DP)

ZOJ 1524 Supermarket

Mr. Jones得到一份购物清单,清单上按顺序写出了需要购买的物品,在买东西的时候要按照顺序弄。。。。另外,题目还给出了,超市中的物品排列情况,买东西的时候还要按照物品的顺序进行购买,显然,可能会有各种不同的购买方案,同时花费也是变化着的,要求输出最少的花费。

设一个V[i]表示,购买前i+1件物品所需要的最少费用,L[i]表示列表上商品的类型 ,K[i]表示超市里的商品类型,P[i]商品价格price。由于要随着商品的排列情况进行选择,从第一件商品开始进行选择,层循环因为用到滚动数组,所以要从M-1开始循环。。。。每找到一个便宜的价格就更新,最终V[M-1]就是结果。。。。      如果这样不好理解的话,可以这样V[i][j]表示用超市中前i个物品,买清单上前个j物品所需花费的最少量。。。 好好想想。。好好想想。。。
(更多…)

继续阅读 →

ZOJ 1107 FatMouse and Cheese(DP)

ZOJ 1107 FatMouse and Cheese

给出一个地图,每个点上有相应数量的cheese,从(0,0)开始,每到一个格子就把这个格子里的cheese吃光,每走一步方向只能是上下左右,每一步最多的格子数为k,每一步之后的一格所包含的cheese数量要比之前一格多…怎样才能使得所得的cheese数最多呢,输出最多的cheese数.

刚开始我想的搜索,但是无门.但是看题之前已经知道是dp了,就一直往那方面想,一开始就想到了从cheese最大点开始,把他相应范围内的最大数更新一下,也就是dp[i][j] = map[i][j] + (与i,j在合法范围内dp[*][*]最大值),dp[i][j]表示从(i,j)开始所能得到的最多cheese。但是想想实现起来比较麻烦。昨天晚上睡觉上微博的时候,突然想到把这些点按照cheese数排列,然后从前往后进行dp,相当于LIS,只不过中间的限制条件比较多。按照这个思路,我写出代码,并且ac掉:
(更多…)

继续阅读 →