Algo_线性dp原理、一维dp原理、dp原理

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,插入到21之间的!!
	' 当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-11 再继续遍历
 	其中的一个应用便是:' 哈密顿 '路径问题。
	给定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里的[下标为23的元素,可以swap,即与顺序无关!]
					[下标为15的元素,也可以swap]
	即这个ans里,所有元素值相同的元素, 可以任意的进行swap操作!!
	
2, 集合问题
	(ans = [2 -> 1] [3 -> 5] 这些数字表示元素的下标)
	' 假如,这个ans 与下标无关,只与该元素的值有关 '
	那么,就会导致:  比如这个ans = [11 -> 18]  [11 -> 18] 这些数字是元素的值
	意味着,这个ans里的[下标为23的元素,可以swap,即与顺序无关!]
					[下标为15的元素,也可以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的第二维度 '
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值