Appearance
背包问题 Knapsnap Problem
- Input:
items with cost and value and a capacity . - Output: A subset of items such that
and is maximized.
| Item | Value | Cost |
|---|---|---|
| iPhone | 8888 | 8888 |
| Algorithm Book | 10000 | 500 |
| Laptop | 8888 | 8500 |
| Hermes | 90000 | 100000 |
A greedy approach
Select the item from larger value-cost ratio(最高性价比)
When the greedy approach fails?
Because the item and value are discrete, we can't divide the item. How to spend the capacity
DP approach
Subproblem:
Transition Formula
What we always do before: Define the state clearly.
: the maximum value we can get by using the first items, and with budget. Two options for item
: - Buy it: We can use at most
budget before item . - Not buy it: We can use at most
budget before item .
- Buy it: We can use at most
Therefore,
when
If
Check the topological order
only depends on states in row . - So we can fill the DP table row by row, from small
to large . - Inside each row, enumerate budget
. - The final answer is
.
| 0 | 1 | 2 | 3 | 4 | 5 | |||
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | |||||||
| 2 | 0 | |||||||
| 3 | 0 | |||||||
| 0 | ||||||||
| 0 |
- Time complexity:
is NOT polynomial!
- Input Size: the unit of bits to represent the input.
by using bits. is . - So the complexity is
, which is exponential.
Some approximal ideas
Some times
Round cost
Round value
Let , then OPT <= nV.
Let
Trasition Formula:
| Ori Value | Approxi value | cost |
|---|---|---|
| 86526 | 86000 -> 86 | 102435 |
| 25473 | 25000 -> 25 | 123543 |
| 87654 | 87000 -> 87 | 234525 |
| 65476 | 65000 -> 65 | 123543 |
| 25477 | 25000 -> 25 | 345756 |
Divide value by
为了让 value-based DP 的状态数变小,可以把每个价值按同一个尺度
然后在缩放后的价值上做 DP。令
所以如果继续使用
状态数为
空间如果只保留上一行,可以降到:
Precision analysis
因为向下取整,每个物品的真实价值和缩放后价值满足:
也就是:
对任意一个包含不超过
设
因此原始价值的下界为:
结论:除以
如果希望得到
在常见假设
此时时间复杂度变为:
Largest Number in k Consecutive Numbers
- Input: A sequence of n numbers
- Output: Find the largest number in k consecutive numbers
朴素的做法 :
使用堆维护:
动态规划 维护一个PLL列表,从前k个数开始,按降序排列,此先后移动一个元素,pop out最前面的,pop in新加入的元素。 新加入的元素在加入时,踢出比它小的数。
初步来看,每次最坏踢出k个数,总时间复杂度仍然是
均摊分析:当一个数(
从Charge的角度来看,每个数只会被pop in一次,比较并pop out一次,因此均摊时间复杂度为
Optimized longest increasing subsequence
在 lecture10 里,LIS 的基础 DP 是:
它每次要枚举所有
Potential Prefixes
定义:
也就是说,sm[len] 不是某一个固定位置的 LIS,而是所有长度为 len 的递增子序列中,最小的结尾值。
为什么取最小结尾值?因为在相同长度下,结尾越小,未来越容易接上新的数。例如长度都是 3,结尾为 5 的序列比结尾为 13 的序列更有潜力继续扩展。
初始化:
Updating sm[len]
处理新元素 len 使得:
那么 len 的递增子序列后面,形成长度为 len+1 的递增子序列:
这正对应图里的两个 case:
- 如果
,它可以 create a longer LIS。 - 如果
,它不能创建更长的 LIS,但可能把某个 sm[len]更新得更小,让之后的数更容易接上。
sm 数组本身是递增的:
所以可以用 binary search 找到更新位置。
Binary Search version
等价写法:在当前 sm[1..L] 中找到第一个满足 sm[pos] >= a_i 的位置 pos,然后令:
如果找不到这样的 pos,说明
text
// Input: sequence a[1..n]
// Output: length of LIS
Function Fast_LIS(a):
L = 0
sm[0] = -infinity
for i from 1 to n:
// find the first pos in [1, L] with sm[pos] >= a[i]
pos = LowerBound(sm[1..L], a[i])
if pos does not exist:
L = L + 1
sm[L] = a[i]
else:
sm[pos] = a[i]
return L这里使用 LowerBound(sm, a[i]) 是为了处理 strictly increasing。如果遇到相同元素,新的元素会替换同一长度的结尾,而不会错误地把长度加一。
Complexity
每个
总时间复杂度:
空间复杂度: