记忆化搜索不依赖于任何外部变量(外部变量即指定义于dfs函数外且值随dfs运行而改变的变量) 
答案以返回值的形式存在,而不能以参数的形式存在 
对于同一组参数,dfs的返回值是相同的 
 
设f i , j f_{i,j} f i , j  
状态转移方程	f i , j = m a x { f i − 1 , k + b i − ∣ a i − j ∣ } f_{i,j}=max \{ f_{i-1,k} +b_i-\lvert a_i-j \rvert \} f i , j  = m a x { f i − 1 , k  + b i  − ∣ a i  − j ∣ } 
注意这里的k是有范围的	j − ( t i − t i − 1 ) × d ≤ k ≤ ( t i − t i − 1 ) × d j-(t_i-t_{i-1})\times d\le k \le (t_i-t_{i-1}) \times d j − ( t i  − t i − 1  ) × d ≤ k ≤ ( t i  − t i − 1  ) × d 
最终式子化为	f i , j = m a x { f i − 1 , k − ∣ a i − j ∣ } + b i = m a x { f i − 1 , k } − ∣ a i − j ∣ + b i f_{i,j}=max \{f_{i-1,k}- \lvert a_i-j \rvert \} +b_i=max \{f_{i-1,k} \} - \lvert a_i-j \rvert +b_i f i , j  = m a x { f i − 1 , k  − ∣ a i  − j ∣ } + b i  = m a x { f i − 1 , k  } − ∣ a i  − j ∣ + b i  
本题中,维护一个单调队列,在加入新的元素的时候,如果前面的元素比它的值小,则弹出前面的元素,直到遇到比它大的才停止,即最后的队列是单调递减的
注意最左边的元素有可能越界了,这时候也要弹出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include  <algorithm>  #include  <cstring>  #include  <iostream>  using  namespace  std ;typedef  long  long  ll;const  int  maxn = 150000  + 10 ;const  int  maxm = 300  + 10 ;ll f[2 ][maxn]; ll a[maxm], b[maxm], t[maxm]; int  n, m, d;int  que[maxn];int  fl = 1 ;void  init ()    memset (f, 207 , sizeof (f));   memset (que, 0 , sizeof (que));   for  (int  i = 1 ; i <= n; i++) f[0 ][i] = 0 ;   fl = 1 ; } void  dp ()    init();   for  (int  i = 1 ; i <= m; i++) {     int  l = 1 , r = 0 , k = 1 ;     for  (int  j = 1 ; j <= n; j++) {       for  (; k <= min(1ll  * n, j + d * (t[i] - t[i - 1 ])); k++) {         while  (l <= r && f[fl ^ 1 ][que[r]] <= f[fl ^ 1 ][k]) r--;         que[++r] = k;       }       while  (l <= r && que[l] < max(1ll , j - d * (t[i] - t[i - 1 ]))) l++;       f[fl][j] = f[fl ^ 1 ][que[l]] - abs (a[i] - j) + b[i];     }     fl ^= 1 ;   } } int  main ()    cin  >> n >> m >> d;   for  (int  i = 1 ; i <= m; i++) cin  >> a[i] >> b[i] >> t[i];      dp();   ll ans = -1e18 ;   for  (int  i = 1 ; i <= n; i++) ans = max(ans, f[fl ^ 1 ][i]);   cout  << ans << endl ;   return  0 ; } 
你有n个物品,每个物品重量为w i w_i w i  v i v_i v i  k i k_i k i  
 
容易想到可以把第i种物品通过二进制拆分,大致拆成log  ( k i ) \log(k_i) log  ( k i  ) 1 1 1 k i k_i k i  k i k_i k i  
还有一种方法是使用单调队列优化,这样的复杂度是最优的,相比于二进制拆分少了log的复杂度
该部分参考了一篇博客 ,在这里补充一下我的理解
使用单调队列优化时,大致的思路还是没有变。用f [ i ] [ j ] f[i][j] f [ i ] [ j ] f [ i − 1 ] [ ] f[i-1][ ] f [ i − 1 ] [ ] f [ i ] [ ] f[i][ ] f [ i ] [ ] 
在第i层循环中,只能购买第i种物品,设第i中物品的体积是v i v_i v i  f [ j ] f[j] f [ j ] j / v i = m j/v_i=m j / v i  = m j % v i j \% v_i j % v i  j % v i , j % v i + v i , j % v i + 2 × v i , . . . , j % v i + m × v i j \% v_i,j \%v_i+v_i,j \% v_i+2 \times v_i,...,j\% v_i+m \times v_i j % v i  , j % v i  + v i  , j % v i  + 2 × v i  , . . . , j % v i  + m × v i  
所以我们第二层循环为余数,即从0 0 0 v i − 1 v_i-1 v i  − 1 j % v i , j % v i + v i , j % v i + 2 × v i , . . . , j % v i + m × v i j \% v_i,j \%v_i+v_i,j \% v_i+2 \times v_i,...,j\% v_i+m \times v_i j % v i  , j % v i  + v i  , j % v i  + 2 × v i  , . . . , j % v i  + m × v i  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for (int  i=1 ;i<=n;i++){ 	cin >>c[i]>>w[i]>>num[i]; 	if (V/c[i]<num[i]) num[i]=V/c[i]; 	for (int  mo=0 ;mo<c[i];mo++) 	{ 		head=tail=0 ; 		for (int  k=0 ;k<=(V-mo)/c[i];k++)  		{ 			int  x=k; 			int  y=f[k*c[i]+mo]-k*w[i]; 			while (head<tail && que[head].pos<k-num[i])head++; 			 			while (head<tail && que[tail-1 ].value<=y)tail--; 			 			que[tail].value=y,que[tail].pos=x; 			tail++; 			f[k*c[i]+mo]=que[head].value+k*w[i];                           		} 	} } 
单调队列的板子题
 
容易想到可以用f(time,x,y)来定义在time时刻,位于(x,y)处走过的最长路径,但是这样的复杂T ∗ M ∗ N T*M*N T ∗ M ∗ N f [ j ] = m a x { f [ i ] + j − i } , i < j f[j]=max\{ f[i]+j-i\},i<j f [ j ] = m a x { f [ i ] + j − i } , i < j f [ j ] − j = m a x { f [ i ] − i } f[j]-j=max\{ f[i]-i\} f [ j ] − j = m a x { f [ i ] − i } f [ i ] − i f[i]-i f [ i ] − i 
其实我们可以发现,像这样的通过求某一个区间的极值来更新后续状态的情况,都可以用单调队列来求解
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <bits/stdc++.h>  using  namespace  std ;typedef  long  long  ll;const  int  INF = 0x3f3f3f3f ;const  int  MAXN = 205 ;const  int  dx[] = {0 ,0 ,0 ,-1 ,1 };const  int  dy[] = {0 ,-1 ,1 ,0 ,0 };int  n,m,ans,sx,sy,k;char  mp[MAXN][MAXN];int  f[MAXN][MAXN];struct  Period {	int  bgn,end,dir; } p[MAXN];		 struct  Node {	int  pos,val; }; deque <Node> q;void  caculate (int  x,int  y,int  dir,int  len) 	q.clear(); 	int  idx = 0 ;          	while (x >= 1  && x <= m && y >= 1  && y <= n){ 		if (mp[y][x] == 'x' ){  		    q.clear(); 		}else { 			while (!q.empty() && q.back().val <= f[y][x] - idx) 			    q.pop_back();  			q.push_back((Node){idx,f[y][x] - idx});             		 			if (idx - q.front().pos > len)  			    q.pop_front(); 			f[y][x] = q.front().val + idx; 			ans = max(ans,f[y][x]); 		} 		x += dx[dir]; 		y += dy[dir]; 		idx++;         	 	} } int  main () 	scanf ("%d%d%d%d%d" ,&n,&m,&sx,&sy,&k); 	for (int  i = 1 ;i <= n;i++){ 		for (int  j = 1 ;j <= m;j++){ 		    cin  >> mp[i][j]; 		    f[i][j] = -100000 ; 		} 	} 	for (int  i = 1 ;i <= k;i++) 	    scanf ("%d%d%d" ,&p[i].bgn,&p[i].end,&p[i].dir); 	f[sx][sy] = 0 ; 	for (int  i = 1 ;i <= k;i++){ 		int  len = p[i].end - p[i].bgn + 1 ; 		switch (p[i].dir){		   			case  1 : 				for (int  j = 1 ;j <= m;j++) 				    caculate(j,n,1 ,len);  				break ; 			case  2 : 				for (int  j = 1 ;j <= m;j++) 				    caculate(j,1 ,2 ,len);  				break ; 			case  3 : 				for (int  j = 1 ;j <= n;j++) 				    caculate(m,j,3 ,len);  				break ; 			case  4 : 				for (int  j = 1 ;j <= n;j++) 				    caculate(1 ,j,4 ,len);  				break ; 		} 	} 	printf ("%d\n" ,ans); 	return  0 ; } 
这道题的状态很好定义,即用f [ i ] [ j ] f[i][j] f [ i ] [ j ] 
首先,如果我们第i天不进行任何操作,则可以得到f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j ] ) f[i][j]=max(f[i][j],f[i-1][j]) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j ] )  
第i天如果执行买入或者卖出的操作,我们只需要考虑第i-W-1天的状态来更新第i天的状态。 
 
为什么考虑第i-W-1天的状态即可? f [ i ] [ j ] ≥ f [ i − k ] [ j ] ( 1 ≤ k ≤ i ) f[i][j]\ge f[i-k][j](1\le k\le i) f [ i ] [ j ] ≥ f [ i − k ] [ j ] ( 1 ≤ k ≤ i ) 
 
根据前面的分析,我们可以写出递推式。f [ i ] [ j ] = m a x { f [ i ] [ j ] , m a x { f [ i − w − 1 ] [ k ] − a p [ i ] ∗ ( j − k ) } } f[i][j]=max\{ f[i][j],max\{ f[i-w-1][k]-ap[i]*(j-k)\} \} f [ i ] [ j ] = m a x { f [ i ] [ j ] , m a x { f [ i − w − 1 ] [ k ] − a p [ i ] ∗ ( j − k ) } } f [ i ] [ j ] = m a x { f [ i ] [ j ] , m a x { f [ i − w − 1 ] [ k ] + b p [ i ] ∗ ( k − j ) } } f[i][j]=max\{ f[i][j],max\{ f[i-w-1][k]+bp[i]*(k-j)\} \} f [ i ] [ j ] = m a x { f [ i ] [ j ] , m a x { f [ i − w − 1 ] [ k ] + b p [ i ] ∗ ( k − j ) } }  
 
分析完毕。这时如果变换一下递推式,得到(以买入时为例):f [ i ] [ j ] = m a x { f [ i ] [ j ] , m a x { f [ i − w − 1 ] [ k ] + a p [ i ] ∗ k } − a p [ i ] ∗ j } f[i][j]=max\{ f[i][j],max\{ f[i-w-1][k]+ap[i]*k\} -ap[i]*j\} f [ i ] [ j ] = m a x { f [ i ] [ j ] , m a x { f [ i − w − 1 ] [ k ] + a p [ i ] ∗ k } − a p [ i ] ∗ j } 
显然可以使用单调队列进行优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h>  using  namespace  std ;typedef  long  long  ll;const  int  N=2e3 +5 ;const  ll inf=1ll <<62 ;ll dp[N][N]; int  T,maxP,W;int  ap[N],bp[N],as[N],bs[N];struct  node {     ll val;     int  pos; }q[N]; int  l,r;int  main ()     scanf ("%d%d%d" ,&T,&maxP,&W);     for (int  i=1 ;i<=T;++i)     scanf ("%d%d%d%d" ,&ap[i],&bp[i],&as[i],&bs[i]);     for (int  i=0 ;i<=T;++i)     for (int  j=0 ;j<=maxP;++j)    		dp[i][j]=-inf;     for (int  i=1 ;i<=T;++i)     {         for (int  j=0 ;j<=as[i];++j)         	dp[i][j]=-ap[i]*j;         for (int  j=0 ;j<=maxP;++j)         	dp[i][j]=max(dp[i-1 ][j],dp[i][j]);         int  t=i-W-1 ;         if (t>0 ){ 			l=r=0 ; 			for (int  j=0 ;j<=maxP;++j){ 				while (l<r&&(j-q[l].pos)>as[i])l++; 				while (l<r&&q[r-1 ].val<=dp[t][j]+1ll *j*ap[i])r--; 				q[r].pos=j; 				q[r++].val=dp[t][j]+1ll *j*ap[i]; 				dp[i][j]=max(dp[i][j],q[l].val-1ll *j*ap[i]); 			} 			l=r=0 ; 			for (int  j=maxP;j>=0 ;j--){ 				while (l<r&&(q[l].pos-j)>bs[i])l++; 				while (l<r&&q[r-1 ].val<=dp[t][j]+1ll *j*bp[i])r--; 				q[r].pos=j; 				q[r++].val=dp[t][j]+1ll *j*bp[i]; 				dp[i][j]=max(dp[i][j],q[l].val-1ll *j*bp[i]); 			}         }     }     ll ans=-inf;     for (int  i=0 ;i<=maxP;++i)     	ans=max(ans,dp[T][i]);     printf ("%lld\n" ,ans);     return  0 ; } 
「Luogu P1855」榨取 kkksc03 
乍一看多了一个限制条件,其实这种题挺简单的,只需要多开一维数组,同时转移两个价值即可
1 2 3 4 5 for  (int  k = 1 ; k <= n; k++) {  for  (int  i = m; i >= mi; i--)         for  (int  j = t; j >= ti; j--)         dp[i][j] = max(dp[i][j], dp[i - mi][j - ti] + 1 ); } 
「Luogu P1757」通天之分组背包 
第一层循环变量是不同的组,第二层是体积,第三层是该组的每一个元素。注意第二层循环要从大到小更新。
1 2 3 4 5 for  所有的组k    for  v=V..0          for  所有的i属于组k       f[v]=max{f[v],f[v-w[i]]+c[i]} 
「Luogu P1064」金明的预算方案 
这种题目可以对于每一个不同的主件都建立一个树。然后对于这个树中的所有元素进行分析
对于包含一个主件和若干个附件的集合有以下可能性:仅选择主件,选择主件后再选择一个附件,选择主件后再选择两个附件……需要将以上可能性的容量和价值转换成一件件物品。因为这几种可能性只能选一种,所以可以将这看成分组背包。
如果是多叉树的集合,则要先算子节点的集合,最后算父节点的集合
这种背包,没有固定的费用和价值,它的价值是随着分配给它的费用而定。在背包容量为V的背包问题中,当分配给它的费用为v i v_i v i  h ( v i ) h(v_i) h ( v i  ) 
定义一个新的变量g i , v g_{i,v} g i , v  
1 2 3 4 5 6 7 8 int  v = V;  for  (从最后一件循环至第一件){   if  (g[i][v]) {     选了第 i 项物品;     v -= 第 i 项物品的价值;   } else      未选第 i 项物品; } 
对于给定的一个背包容量、物品费用、其他关系等的问题,求装到一定容量的方案总数。
这种问题就是把求最大值换成求和即可
即d p i = ∑ ( d p i , d p i − c i ) dp_i=\sum{(dp_i,dp_{i-c_i})} d p i  = ∑ ( d p i  , d p i − c i   ) 
注意初始状态定义为d p 0 = 1 dp_0=1 d p 0  = 1 
定义dp状态f i , j f_{i,j} f i , j  
这样修改之后,每一个dp状态都可以用g i , j g_{i,j} g i , j  
f i , j f_{i,j} f i , j  g i , j g_{i,j} g i , j  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 for  (int  i = 0 ; i < N; i++) {  for  (int  j = V; j >= v[i]; j--) {     int  tmp = max(dp[j], dp[j - v[i]] + w[i]);     int  c = 0 ;     if  (tmp == dp[j]) c += cnt[j];                            if  (tmp == dp[j - v[i]] + w[i]) c += cnt[j - v[i]];       dp[j] = tmp;     cnt[j] = c;   } } int  max = 0 ;  for  (int  i = 0 ; i <= V; i++) {  max = max(max, dp[i]); } int  res = 0 ;for  (int  i = 0 ; i <= V; i++) {  if  (dp[i] == max) {     res += cnt[i];     } } 
第K优解比求最优解的复杂度上多一个系数K。其基本思想是,将每个状态都表示成有序队列,将状态转移方程中的max/min转化成有序队列的合并。
这里定义dp状态为f i , j , k f_{i,j,k} f i , j , k  f i , j , 1... K f_{i,j,1...K} f i , j , 1 . . . K  f i − 1 , j , 1... K f_{i-1,j,1...K} f i − 1 , j , 1 . . . K  f i − 1 , j − v i , 1... K + w i f_{i-1,j-v_i,1...K}+w_i f i − 1 , j − v i  , 1 . . . K  + w i  
背包问题九讲 - 崔添翼