Loading

背包DP

背包DP

  • 0/1背包
  • 完全背包
  • 多重背包

一般是给出一些“物品”,每个物品具有一些价值参数和花费参数,要求 在满足花费限制下最大化价值或者方案数

0/1背包问题

给出 \(n\) 个物品,每个物品有 \(V_i\) 的价值和 \(W_i\) 的费用,我们总共有 \(m\) 块钱,求 最多能得到多少价值的物品

\(N<=10^3,m<=10^3\)

solution

每个物品只用了一遍

状态:

$ f[i][j]$ 表示前 \(i\) 个物品,用了 \(j\) 的花费得到的最大的价值

转移:

看第 \(i\) 个物品是否使用

\(f[i][j]=max(f[i-1][j] , f[i-1][j-w[i]]+v[i])\)

时间复杂度:

\(O(N*M)\)

   memset(f,-0x3f,sizeof(f));
   f[0][0] = 0;
   for(int i = 1;i <= n; i++){
   	  for(int j = 0;j < w[i]; j++)f[i][j] = f[i - 1][j];
   	  for(int j = w[i];j <= m; j++)f[i][j] = max(f[i - 1][j],f[i - 1][j- w[i]] + v[i]);
   }

1.输出最大值得方案数

因为要得到最大值,所以再放物品的时候,留的空间正好够放整个物品,

 for(int i=0;i<=n;i++) //注意这里要从0开始 
   f[i][0]=1; 
 for(int i=1;i<=n;i++)
   for(int j=1;j<=m;j++)
   {
   	f[i][j]+=f[i-1][j];
   	if(j>=a[i])
   	  f[i][j]+=f[i-1][j-a[i]];
   }

2.输出方案:

逆序思维倒序输出即可

对于\(f[i][j]=max(f[i-1][j] , f[i-1][j-w[i]]+v[i])\) 值需要判断一下\(f[i][j]= ~!=f[i-1][j-w[i]]+v[i]\)

如果等说明第 \(i\) 个物品一定就在背包中,只需要枚举 \(i\) 就好了

滚动数组优化*

我们发现每组 \(f\) 更新的时候只和 \(i-1\) 有关,所以直接开一位数组转移即可

   memset(f,-0x3f,sizeof(dp));
   f[0] = 0;
    for (int i = 1;i <= n; i++){
      for (int j = m;j <= w[i]; j--)f[j] = max(f[j],f[j - w[i]] + v[i]);  
	}

为什么倒着枚举,废了我一晚上

为保证每个物品只能使用一次,我们倒序遍历所有 \(j\) 的值,保证在更新 \(f[j]\) 时候 \(f[j - w[i]]\) 不会被更新,这样保

证枚举背包空间的时候,都会用没有装这个物品的背包状态来转移;而如果正序枚举的时候, $ f[j - w[i]]$ 有可

能已经用这个物品更新过一次了,这样这个物品就会被反复用来两次,不符合规定

输出方案?

要再用个二维数组存起来,\(path[i][j]\)

for(int i = 0 ; i < N ; i++)
{
	for(int j = V ; j >= c[i] ; j++)
		if(f[j-c[i]]+w[i] > f[j])
		{
			f[j] = f[j-c[i]]+w[i];
			path[i][j] = 1;
		}
}

输出方案

如果 \(path[i][j] = 1;\) 说明 \(i\) 物品一定在 \(j\) 的空间状态下达到的最优解

输出就倒序输出就好了

int i = N, j = V;  
while(i > 0 && j > 0)  
{  
    if(Path[i][j] == 1)  
    {  
        cout << c[i-1] << " ";  
        j -= c[i-1];  
    }  
    i--;  
}

2.最大值方案数:

 f[0] = 1;
   for (int i = 1;i <= n; i++){
   	  for (int j = m;j >= w[i]; j--){
   	  	    f[j] += f[j - w[i]];
		}
   }

完全背包

每一类物品可以选无限个

solution

状态和01背包一个样

转移是关键

1 .如果不选:那么很明显 \(f[i][j] = f[i-1][j]\)

2 .如果选:先给个式子:

\[f[i][j]=f[i][j-w[i]]+v[i] \]

选的话,背包中应该至少出现一个这种物品,所以枚举到这个物品的时候因为 \(f[i][j - w[i]]\) 可能已经有了这个这种物品也可能没有,所以要再留出这个空间来存放这个物品,这个物品就有可能选多次

所以完全背包转移

\[f[i][j]=max(f[i][j-w[i]]+v[i], f[i-1][j]) \]

node

   memset(dp,-0x3f,sizeof(dp));
   dp[0][0] = 0;
   for(int i = 1;i <= n; i++){
   	  for(int j = 0;j < w[i]; j++)f[i][j] = f[i - 1][j];
   	  for(int j = w[i];j <= m; j++)f[i][j] = max(f[i - 1][j],f[i][j- w[i]] + v[i]);
   }

01背包和完全背包区别(特别小

\[~~~~~~01背包: f[i][j]=max\lbrace~f[i-1][j],~~f[i-1][j-w[i]]+v[i]\rbrace \\ 完全背包:f[i][j]=max\lbrace~f[i-1][j],~~f[i][j-w[i]]+v[i] \rbrace \]

01背包: \(f[i-1][j-w[i]]+v[i]\) 前一个空间已经是 \(j-w[i]\) 了,枚举到这个物品的时候用刚好盛下它的状态来转移,所以这个物品运用这个状态就只用了一次

完全背包:\(f[i][j-w[i]]+v[i]\) 当所用空间为 \(j - w[i]\) 也就是说枚举到 \(i\) 的时候 \(i\) 可能已经用过了,所以 \(i\) 就有可能用多次

优化:

根据上面的 01背包的保证每个物品只能选一个,所以倒叙枚举来避免选择同一个物品多次的情况,而现在正序枚举就好了

node

   memset(f,-0x3f,sizeof(dp));
   f[0] = 0;
    for (int i = 1;i <= n; i++){
      for (int j = w[i];j <= m; j--)f[j] = max(f[j],f[j - w[i]] + v[i]);  
	}

多重背包

对于每个物品可以买最多能用 \(t[i]\)

每个最多选 \(t[i]\) 次暴力转移就好了,再枚举每个物品选多少次

\[f[i][j] = max{ ~f[i-1][j-w[i]*k] + v[i]*k~ |~ k<=t[i]} \]

复杂度\(O( N*M*t[i] )\)

注意:k 枚举的时候不能超过背包的上限 \(k*w[i] <= j\)

node

memset(f,-0x3f,sizeof(f));
   f[0][0] = 0;
   for(int i = 1;i <= n; i++)
   	  for(int j = 0;j <= m; j++){
   	    f[i][j] = f[i-1][j];
   	    for(int k = 1 ;k <= c[i] && k*w[i] = j;k++)
   	      f[i][j] = max(f[i][j],f[i-1][j-w[i]*k] + k*v[i]);
  	  }

同理:

memset(dp,-0x3f,sizeof(dp));
   f[0] = 0;
   for(int i = 1;i <= n; i++)
   	  for(int j = m;j >= 0; j--){
   	    for(int k = 1 ;k <= c[i] && k*w[i] = j;k++)
   	      f[j] = max(f[j],f[j-w[i]*k] + k*v[i]);
  	  }

多重背包优化

转化成01背包

二进制,考虑把第 \(i\) 种物品分成若 \(n[i]\) 件01背包中的物品

将第 \(i\) 种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为\(1,2,4,...,2^{k-1},n[i]-2^{k}+1\),且 \(k\) 是满足 \(n[i]-2^k+1>0\) 的最大整数。例如,如果 \(n[i]\) 为 13 ,就将这种物品分成系数分别为1,2,4,6的四件物品

一句话:也就是说原来你选这个物品的所有情况就都可以用这些分的物品给表示出来(原因:它们的二进制拼接可以形成这个区间的所有的情况,如下)

1, 2, 4, 8, 16, 32 : 0~63

1,10,100,1000,10000,100000

分成的这几件物品的系数和为 \(n[i]\),表明不可能取多于 \(n[i]\) 件的第 \(i\) 种物品

时间复杂度:

\(O(n*log(t[i])*m)\)

单调队列优化

\[f[i][j] = max(f[i-1][j-w[i]*k] + v[k]|k \leq [i]) \]

发现 它的 \(j\) 和能转移过来的 \(j-w[i]*k\) 在模 \(w[i]\) 意义下是同余的(余数相同){因为(w[i]*k)%w[i]==0$所以就可以看做 j }

也就是说可以对于第二维按照模 \(w[i]\) 的同余类进行分类,不同 类之间不会互相影响

状态:

\(f[j]=f[i-1][j*w[i]+r]\) r 是我们枚举模 \(w[i]\) 的每一类,也就是mod w[i]的每一种余数的,也就是说我们可以对于第二维按照模 \(w[i]\) 的同余类进行分类,不同 类之间不会互相影响

\(dp[j]=f[i-1][j*w[i]+r]\) r是我们枚举模 \(w[i]\) 的一个类

\[f[i][j*w[i]+r] = max(dp[k] + (j - k)*v[i]~|~j-k\leq t[i])\\ f[i][j*w[i] + r] = max(dp[k] - k*v[i]~|~j-k\leq t[i])+j*v[i] \]

化简的过程

把转移方程中的 \(j\)\(j*w[i]+r\) 代替,注意:这两个 j 不一样

把原式的 \(k\)\(j - k\) 代替,那么取值范围就成了 \(j-k\leq t[i])\):都是为了方便化简

\[f[j]=f[i-1][j*w[i]+r]\\ \\ f[i][j*w[i]+r] = max\lbrace ~f[i-1][j*w[i]+r -(j-k)*w[i]] + (j-k)*v[i] | j-k\leq t[i]~\rbrace\\ \\ f[i][j*w[i]+r] = max(dp[k] + (j - k)*v[i]~|~j-k\leq t[i])\\ \\ f[i][j*w[i] + r] = max(dp[k] - k*v[i]~|~j-k\leq t[i])+j*v[i] \]

我们发现化简到最后一项的时候,范围是个区间,并且最后面是个常数,其转移的值只和 \(dp[k] - k*v[i]\) 有关,并且要求求最大值,所以应该维护 \(dp[k] - k*v[i]\) 是个单调递减的,可以用单调队列优化

复杂度:

单调队列均摊是 $ O(m) $

总复杂度就是 \(O(n*m)\) 的了

分组背包

一共有 \(n\) 组,每组有 \(size[i]\) 个物品,第i组第j个物品的费用为 \(w[i][j]\),价值 \(v[i][j]\),每个组里的物品是互斥的,意味着你在一组物品中只能选择一个 物品,求花费小于等于m能得到的最大价值

Size之和$\leq\(1000,\)m \leq 1000$

状态:

$ f [i][j]$ 表示选第 \(i\) 组,背包容量还剩 \(j\) ,\(w[i][k]\) 表示第 \(i\) 的第 \(k\) 个背包的容量, \(v[i][k]\) 表示第 \(i\) 组的 \(k\) 背包的价值

转移

用上一组的选择转移就好了

\[f[i][j] = max_{k = 1}^{size_i} (f[i-1][j-w[i][k]] + v[i][k]) \]

   memset(f , -0x3f,sizeof(f));
   f[0] = 0;
   for (int i = 1; i <= n; i++){
   	 for (int j = m;j >= 0; j--)
   	    for (int k = 1;k <= size[i]; k++)
   	    f[j] = max(f[j],f[j-w[i][k]] + v[i][k]);
   }

总结:

背包问题考虑三个条件:

容量:要转移的状态数组

费用\(f[i]---->f[i+w[k]]\),状态转移的跨度

价值 :\(f\) 数组要维护的东西

栗题

一些人住旅馆,满足两个条件

a.不同性别的人如果不是夫妻那么不能住一个房间。

b.一对夫妻如果住在一起,那么房间不能安排其他的人进去哪怕房间没盛满人

求所有人都有房间住的最小花费

\(M\):参加旅行的男性人数、 \(f\):参加旅行的女性人数、\(r\):旅馆的房间数、 \(c\):这些男女中有多少对夫妻、\(B_i\):每个房子容纳人数和、\(P_i\):每个房子 价格。注意每一个人不是单身就是和他/她唯一的妻子/丈夫一起参加旅行。

\(0\leq m,f,r\leq 300,0\leq c\leq Min(m,f),0\leq P_i\leq10~~~~~2\leq Bi\leq 300\)

solution

既然是背包专题就要往背包那里想了,找条件??容量,物品, 费用,价值

那么在这里,几个 人对应就是 \(f\) 的数组下标,每个房间就是一个物品,房间支出就是物品的权值

因为房间的费用是我们要求的东西,即存在数组 \(f\) 中,所以我们应该把他当做物品

发现如果两个或者两个以上的夫妻住在一起的话,还不如交换,因为交换之后每个房间都只有男的和女的,其他人还能住,所以住的时候,至多保证一对夫妻住在一起即可

状态:

\(f[i,j,k,0]\) 表示前 \(i\) 个房间住 \(j\) 名男性 \(k\) 名女性并且没有夫妇住在一起的最小花费

\(f[i,j,k,1]\) 表示前 \(i\) 个房间住 \(j\) 名男性 \(k\) 名女性并且有一对夫妇住在一起的最小花费

\[f[i,j,k,0]=min\lbrace f[i-1,j,k,0],f[i-1,j-v[i],k,0]+p[i],f[i-1,j,k-v[i],0]+p[i]~\rbrace \\ f[i,j,k,1] = min\lbrace f[i-1,j,k,1],f[i-1,j-v[i],k,1]+p[i],f[i-1,j,k-v[i],1]+p[i],\\f[i-1,j-1,k-1,0]+p[i]\rbrace \]

\(f[i-1,j-1,k-1,0]+p[i]\) 这一项代表有可能新来的一位也构成夫妻,就把原来的那对分开,凑成新的一对==

栗题

给你 \(N\) 颗宝石,每颗宝石都有重量和价值 \(V\)。要你从这些宝石中选取一些 宝石,保证总重量不超过 \(W\),且总价值最大为,并输出最大的总价值,每颗宝石的重量符合 \(a*2^b\)

\(V<=10^9~~~ a<=10~~~ b<=30\)

solution

\(W\) 很大,但是 \(a\)\(b\) 都特别小,所以我们可以用 \(2^b\) 分组, 把我们可以先按照 \(b\) 值从小到大排序,在阶段内 \(b\) 值都相同,直接忽略不足 \(2^b\) 的部分

状态:

\(f[i][j]\) 表示前 \(i\) 个物品,剩余的能用重量\(j*2^b\) 的最大价值

转移:

\[f[i][j] = \lbrace~f[i-1][j],f[i-1][j+a]+v[i]~\rbrace \]

时间复杂度:

\(O(n*1000)\)

栗题

给出 \(n\) 个化学反应,每个反应消耗 \(a[i]\) 升氧气和 \(b[i]\) 升氢气,可以得到 \(w[i]\) 的价值,现在总共有 \(X\) 升氧气和 \(Y\) 升氢气,求我们最多可以得到多少价值

\(n,a[i],b[i],X,Y<=100\)

状态:

因为这多了一个限制条件:所以背包的状态应该设也多设置一维

\(dp[i][x][y]\) 表示前 \(i\) 个物品,消耗了 \(x\) 升的氧气, \(y\) 升的氢气

转移:

\[dp[i][x][y] = max \lbrace dp[i][x][y],dp[i - 1][x-a[i]][y-b[i]] + w[i]\rbrace \]

posted @ 2021-02-06 19:46  Dita  阅读(100)  评论(6编辑  收藏  举报