Base
对于一维数组A = [1, 2, 3, 4, ..., n]
' 我们定义集合是: 从A中选出一些元素(不可重复选), 至少选1个。 比如一个集合可以是: 134 '
' 集合分为: 与顺序有关、无关的集 '
ans由1个集合所决定
该ans集合的长度==n
根据该集合是否与顺序有关,再进行划分 (' 所谓的顺序是: 元素的<下标>的大小顺序 '):
1,与顺序无关
因为,与顺序无关,自然ans可以是[1,2,3...n]
这肯定没有意义,因为从最初的[1,2,3...n]本身,就可以得到ans
2,与顺序有关 ('重要应用! ')
比如,答案是[3,2,1,4,5....]
朴素算法肯定是O(n!)全排列暴力枚举。
你可能会想到: 比如对于ans=[2 -> 1]这个顺序 (即,不是有序的)
虽然我们的dp,先遍历的是1 后遍历2 (但,ans是2在先,1在后)
是否可以: 在dp遍历到2时(此时dp里已经存了dp[1]的值)
此时,我们是否可以让这个2 '往前插'到1前面呢??
其实是不可以的, dp是: [pre_st]
1,pre_st是有序的[1->2->x](对应 线性dp)
2,pre_st是无序的[3->2->1->x] (对应 状压dp)
对于当前的元素cur,要更新cur_st。 这个cur,一定是插入到pre_st的'后面'!!!
即一定是x的后面,cur_st = [1->2->x]->cur 或 [3->2->1->x]->cur
这个概念,贯穿整个dp的本质!!
ans = [2 -> 3 -> 1]
如果是'线性O(n)dp', 你在3时,比如有dp[pre_st] = [2->1]
你是无法做到: 把这个3,插入到2和1之间的!!
' 当ans不是有序的时, 此时,只有 (状压dp)可以解决!! '
即,比如ans = [2 -> 3 -> 1]
他对应的状压st为[111], 然后,暴力枚举cur_ed (cur为st中的1)
当我们枚举到cur_ed==1时,此时pre_st = 110(即23)
那么: dp[111] = dp[110] + xx (这就得到了:2 -> 3 -> 1这个无序的dp)
但当n很小时,一定要想到'状压dp'!!!
此时的状压dp, 是O(2^n * n1 * n2)的时间,(n*n可以用lowbit优化)
对于一个st, n1是这st里的所有的1, n2为在n1选了某个1后 剩余的n-1个1 再继续遍历
其中的一个应用便是:' 哈密顿 '路径问题。
给定n个点[0,1,2,3,...,n-1](点号必须是这个范围,否则需要映射到这个区间)
题目会给定一个beg起点, 求beg到[0,1,2,...,n-1]每个点的 最短/长的 哈密顿路径长度
' 所谓哈密顿路径为: 比如3为起点 2为终点 n为5, 则:[3->0->1->4->2] 便是哈密顿路径'
' 即, 哈密顿路径: 一定经过所有的n个点,而且 每个点只会经过一次 '
如果n=20,则暴力n!是超时的。
但为什么会想到使用dp呢? 为什么dp可以解决这个问题呢?
' 一定要记住dp的本质: 用1个st状态,同时表示着 很多的局面cases;'
' dp[st] = 这些cases中的最优解,即此时的dp[st]中的st,其实是cases中的某一个 '
比如st = 7 (二进制是111), 比如他表示的局面有:[012] [021] [102] [120] [201] [210]
这些局面的对应的二进制都是111,所以可以使用1个st=7,来含括这么些局面
但这些局面对应的ans不同, 可能[120]这个局面的ans是最优的。
那么,此时这个st=7, 即dp[7]的答案 就是[120]这个局面的答案(7就表示[120]这个局面)
再比如,求LIS时的dp[i][j]为: 前i个元素中,以第[j]个元素为结尾的子序列
比如dp[5][3]这个st, 他可以对应的局面:[3] [23] [13] [03] [123] [023] [xx3]
可以发现,一个dp[5][3]这1个st,可以对应着 很多很多的局面。 '这便是dp本质'
' 1, 如果题目不是(最多、最少、最优) 不涉及到(最)的问题 '
' 2, 如果题目不是(求方案数),因为一个st包含的局面,求方案数 正好以一代多 '
' 3, 如果你的dp的某个状态st,都无法 含括很多的局面 (比如,1个st仅仅对应1个局面) '
' 那么,肯定没有必要使用dp。 因为dp的本质就是: 1个st,表示着很多的局面 '
' 以上的12情况, 几乎就可以确定 不是dp的问题 '
而此时这个哈密顿问题, (符合'最'问题)(符合{用st=111,可以表示很多的局面}
MST(dp, valid);
dp[1 << beg][beg] = 0;
FOR(st, (1<<beg) + 1, (1<<n) - 1, 1){ ' 注意是从(1<<beg)+1开始的 '
if( (st >> beg) & 1 == 0 ){ continue; }
' 1,所有st,其beg这个bit位 必须是1 (因为路径的起点必须是beg,即st里一定有beg)'
' 2, 此时,st里一定有>=2个1 '
FOR(cur_ed, 0, n-1, 1){ ' 找st中,所有除了beg的 bit位为1的bit '
if( (cur_ed != beg) && ( (st>>cur_ed) & 1 != 0 ) ){
int pre_st = st ^ (1 << cur_ed);
FOR(pre_ed, 0, n-1, 1){ ' 这个两个FOR循环,都可以用lowbit来优化 '
if( ( (st >> pre_ed) & 1 != 0 ) && (dp[pre_st][pre_ed] != -1) ){
MIN( dp[st][cur_ed], dp[pre_st][pre_ed] + dist[cur_ed][pre_ed] );
}
}
}
}
}
该ans集合的长度<=n
' 这个情况是比较常见的, 比如LIS问题就是属于这个分支的 '
根据该集合是否与顺序有关,再进行划分 (' 所谓的顺序是: 元素的<下标>的大小顺序 '):
1,与顺序无关( '重要!!' )
很多简单线性dp问题,都是这个分支。
因为与顺序无关,即就可以当做这个集合是有序的 '这个思路非常重要!'
' 即: ans由(某一个)集合所决定,且这个一个集合可以是有序的 '
比如,LIS(最长单调子序列的长度):
1,ans只涉及到这1个最优秀的子序列,所以属于 'ans仅由1个集合所决定'这一分支
2,这1个集合里的元素,是有序的 或者是 可以是有序的
比如,背包(选一些物品在v<=V的前提下,求最大的w)
这也是: 1,这些物品 构成1个集合,ans只取决于这1个集合,不会是多个集合
2, 这些物品,的下标的顺序,是无关紧要的(无关的意思是: 可以是有序递增的)
' 这类问题,几乎都是O(n * k)的时间: n是要从前往后 顺序[0->1->2...]的遍历整个数组 '
' k是由题目ans的要求所决定的, 比如背包问题中,k为 所选物品的体积。 '
2,无序
比如,答案是[3,2,1,4,5...n]
理论上是 O(n!),即暴力枚举
当n很小时, 同样是使用'状压dp'
ans由>1个集合所决定
这些ans集合的总长度==n
这些ans集合的顺序无关
所有集合内的元素顺序无关
(ans由>=2个集合所决定,集合内元素'可以'为有序)
(不同集合间,顺序无关)
比如有[1,2...7]共7个元素
可以继续划分分类为:
1, ans为: [1,2] [3,4,5] [6,7] (即,前集合的最后 < 后集合的最前)
' 由于整体都是有序的。 所以,可以用O(n)的线性dp来处理 '
2, ans为: [1,7] [2,6] [3,4,5]
' 这严格来讲, 并不能说是: 集合间是有序的 '
因为,集合间 其实并不是有序的。
这种情况暂不分析, 大概率是状压dp, 线性dp应该是解决不了的
所有集合内的元素顺序有关
(ans由>=2个集合所决定,集合内元素是'无序'的!!)
(不同集合间,顺序无关)
比如有[1,2...7]共7个元素
ans为: [7,1] [5,4,3] [6,2]
大概率是状压dp
这些ans集合的顺序有关
这些ans集合的总长度<=n
TODO
线性/集合 状压dp
不管是线性问题的状压dp(即ans由1个集合决定)
还是说集合问题的状压dp(即ans由>1个集合决定)
时间都是O( 2^n * n * k ) ' k为dp的第二维度 '
如果,这个是超时的; 但n又不是很大(比如n=30左右)
解决办法: (仍然是状压dp,但需要进行 特殊hash)
1, 线性问题 (ans = [2 -> 1 -> 3 -> 5] 这些数字表示元素的下标)
' 假如,这个ans 与下标无关,只与该元素的值有关 '
那么,就会导致: 比如这个ans = [11 -> 18 -> 11 -> 18] 这些数字是元素的值
意味着,这个ans里的[下标为2和3的元素,可以swap,即与顺序无关!]
[下标为1和5的元素,也可以swap]
即这个ans里,所有元素值相同的元素, 可以任意的进行swap操作!!
2, 集合问题
(ans = [2 -> 1] [3 -> 5] 这些数字表示元素的下标)
' 假如,这个ans 与下标无关,只与该元素的值有关 '
那么,就会导致: 比如这个ans = [11 -> 18] [11 -> 18] 这些数字是元素的值
意味着,这个ans里的[下标为2和3的元素,可以swap,即与顺序无关!]
[下标为1和5的元素,也可以swap]
即这个ans里,所有元素值相同的元素, 可以任意的进行swap操作!!
' 因为: ans与下标无关。 所以,我们可以把关注点 放到 元素值上 '
' 且,前提所有元素值 >= 0, 而且不能太大 '
元素值 cont
0 x
1 y
2 z
. .
最大元素值Ma
xyz..之和==n
' 问题变为了: (特殊进制)问题。 可以去(特殊进制)CDSN里回顾下 '
即,这个st有Ma位 (但他和二进制里的 st有n位不同!!! )
因为,比如st有1000位(但很多位上,他的cont {cont为改为的进位值} 都是0)
比如,st有1000位。 但[0,998]的cont都是0, 只有[999]位的cont=5
ma_st = (1*1*..*1) * (5+1) = 6;
for(st_i = 0; st_i< ma_st; ++st_i){
FOR(i, 0, 999, 1){
int cur_bit = st_i / poww[i] % ma[i];
if( cur_bit == 1 ){
...
}
}
}
当st_i == 1时,其实他对应的st是: [1, 0,0,0,0...000]
' 注意, 我们for循环的st_i是个10进制数,而他所代表的st是一个长为1000位的(特殊进制)数 '
st_i 和 st是不同的!!!
时间为: O( ma_st * ma_num * k )
ma_st为: 所有的cont的乘积(这个值是很难判断的,越平均时 会越大)
ma_num为; 元素值的最大值( 即st的位数 )
k为: dp的第二维度
' 前提是 元素值 >= 0 '
-------------------
如果还不行,(要么是超时, 要么是元素值有负数)
此时,题目基本会有一个mod
而且对于ans = [a->b] [c->d] (abcd都是元素值,因为已知ans与下标无关)
' 假如,可以证明: 你放a 和 放a%mod,是完全一致的!! '
那么,就可以 进一步的优化:
元素值%mod后: cont
0 x
1 y
2 z
.. ..
mod-1 ..
xyz..之和 == n
算法和上面是一样的,都是 ' 特殊进制 '的 状压dp
st_ma = x * y * z * ...
O( st_ma * mod * k )
' k取决于 dp的第二维度 '