单调队列优化多重背包(含构造问题<POJ 1742 coin>)
2017-09-07 22:30
316 查看
1.前言:
注: 本文中,用v[ i ] 表示物体的价值,w[ i ]表示物体的代价,c[ i ]表示物体的数量上限。
多重背包问题应该是动态规划的基础内容吧,我们先回顾一下多重背包的公式:
dp[ i ][ j ]表示选择到第 i 件物品,总代价为 j 时所获得的最大价值总和。
那么有:dp[ i ][ j ] = max( dp[ i-1 ][ j - k*w[i] ] + k*v[i] ); (0 <= k <= c[ i ])
滚动优化后有: dp[ j ] = ( dp[ j - k*w[i] ] + k*v[i] ); ( j : maxn – > w[i] )
显然,我们要枚举 i、j、k 三维,时间复杂度为O( n^3 )。
这样的时间复杂度很多时候是不能满足题目所需的。
解决办法有两种:二进制优化 与 单调队列优化。
今天在这里,我们就来看看单调队列的优化,它能使时间复杂度降到O( n^2 )。
2.公式的推演:
下面我们来对多重背包问题的公式进行变形
dp[ i ][ j ] = max( dp[ i-1 ][ j - k*w[i] ] + k*v[i] );
我们令 j = a*w[i] + b
代入原式中: dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ a*w[i]+b - k*w[i]] + k*v[i] );
合并同类项:dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ (a-k)*w[i]+b ] + k*v[i] );
分离常项:dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ (a-k)*w[i]+b ] ) + k*v[i];
最关键的一步: 令 a - k = t !
所以 k = a - t
代入原式中: dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ t*w[i]+b ] ) + (a-t)*v[i];
拆开:dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ t*w[i]+b ] ) + a*v[i] - t*v[i];
移项,得到最终式子:
dp[ i ][ a*w[i]+b ] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i]) ;
3.优先队列的实现:
刚刚得到的式子: dp[ i ][ a*w[i]+b ] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i]) ;
观察左右式子,就会发现一个很神奇的事情:左右两边的式子是相等的!!!
先把代码贴出来:
我们一步一步的看( j = a*w[i] + b):
//枚举物体
//枚举剩余类(即余数)
//枚举件数(即a的值)
//如果从队首转移所需件数大于件数上限,弹出队首元素
关键一步:
//如果队尾元素对应值还小于当前对应值,弹出队尾元素,即弹到队尾大于当前元素对应值为止。
//这个操作有必要解释一下为什么。
//由于我们按照顺序添加,前面的元素一定件数较少,更容易出现件数不够的现象(见上“¥”操作)。
//而如果价值又较低,又会被先淘汰,那么这个元素就是没有意义的,要弹出。
//dp[ i ][ a*w[i]+b ] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i]) ;
//我们的初始公式移项计算 dp[ i ][ a*w[i]+b ];
4.多重背包变式:能否构造问题
简单来说,就是给你n个物体,问在总代价小于等于 J 的情况下,可以构造出价值总和为1~K中的多少个值。
题目其实就是POJ 1742 coin
传送门:http://poj.org/problem?id=1742
这个问题其实相对于前面的问题反而还简单了。
我们构造一个队列,存放可转移元素。
如果数量不够,队首出队。
这样的话只要队列中有元素,就说明可以转移。
代码:
5.尾言:
通过对多重背包的优化,大家应该可以看到单调队列的功效。
但在最后,我要说的是:单调队列有时候也可以优化DP!!
根据我自己的经验,一般可以这么做的DP有两个特点:
1> 涉及 取max 或者 取min
2> 通过我们的化简,可以将DP式左右两边化成同一形式。
不过单调队列对DP的优化,可就要根据具体题目来了,这也是我这里不赘述的原因。
希望大家能够自己去钻研这种题型,最后,希望我的一点见解能对大家有所帮助,谢谢观看。
注: 本文中,用v[ i ] 表示物体的价值,w[ i ]表示物体的代价,c[ i ]表示物体的数量上限。
多重背包问题应该是动态规划的基础内容吧,我们先回顾一下多重背包的公式:
dp[ i ][ j ]表示选择到第 i 件物品,总代价为 j 时所获得的最大价值总和。
那么有:dp[ i ][ j ] = max( dp[ i-1 ][ j - k*w[i] ] + k*v[i] ); (0 <= k <= c[ i ])
滚动优化后有: dp[ j ] = ( dp[ j - k*w[i] ] + k*v[i] ); ( j : maxn – > w[i] )
显然,我们要枚举 i、j、k 三维,时间复杂度为O( n^3 )。
这样的时间复杂度很多时候是不能满足题目所需的。
解决办法有两种:二进制优化 与 单调队列优化。
今天在这里,我们就来看看单调队列的优化,它能使时间复杂度降到O( n^2 )。
2.公式的推演:
下面我们来对多重背包问题的公式进行变形
dp[ i ][ j ] = max( dp[ i-1 ][ j - k*w[i] ] + k*v[i] );
我们令 j = a*w[i] + b
代入原式中: dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ a*w[i]+b - k*w[i]] + k*v[i] );
合并同类项:dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ (a-k)*w[i]+b ] + k*v[i] );
分离常项:dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ (a-k)*w[i]+b ] ) + k*v[i];
最关键的一步: 令 a - k = t !
所以 k = a - t
代入原式中: dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ t*w[i]+b ] ) + (a-t)*v[i];
拆开:dp[ i ][ a*w[i]+b ] = max( dp[ i-1 ][ t*w[i]+b ] ) + a*v[i] - t*v[i];
移项,得到最终式子:
dp[ i ][ a*w[i]+b ] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i]) ;
3.优先队列的实现:
刚刚得到的式子: dp[ i ][ a*w[i]+b ] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i]) ;
观察左右式子,就会发现一个很神奇的事情:左右两边的式子是相等的!!!
先把代码贴出来:
for(i = 1; i <= N ; i ++) { for(b = 0 ; b <= w[i]-1 ; b ++) { hd = 1; tl = 0; for(a = 0 ; a*w[i]+y <= M ;a ++) { int now = dp[i-1][a*w[i]+b]-a*v[i]; while( (a-lk[hd])>c[i] && hd<=tl)hd++; while( (dp[i-1][lk[tl]*a[i]+b]-lk[tl]*v[i])<=now && hd<=tl)tl--; lk[++tl] = k; dp[i][a*w[i]+b] = dp[i-1][lk[hd]*w[i]+b]-lk[hd]*v[i]+a*v[i]; } } }
我们一步一步的看( j = a*w[i] + b):
for(i = 1; i <= N ; i ++)
//枚举物体
for(b = c1e2 0 ; b <= w[i]-1 ; b ++)
//枚举剩余类(即余数)
for(a = 0 ; a*w[i]+y <= M ;a ++)
//枚举件数(即a的值)
while( (a-lk[hd])>c[i] && hd<=tl)hd++; ----------------------------------------“¥”
//如果从队首转移所需件数大于件数上限,弹出队首元素
关键一步:
while( (dp[i-1][lk[tl]*a[i]+b]-lk[tl]*v[i])<=now && hd<=tl)tl--;
//如果队尾元素对应值还小于当前对应值,弹出队尾元素,即弹到队尾大于当前元素对应值为止。
//这个操作有必要解释一下为什么。
//由于我们按照顺序添加,前面的元素一定件数较少,更容易出现件数不够的现象(见上“¥”操作)。
//而如果价值又较低,又会被先淘汰,那么这个元素就是没有意义的,要弹出。
dp[i][a*w[i]+b] = dp[i-1][lk[hd]*w[i]+b]-lk[hd]*v[i]+a*v[i];
//dp[ i ][ a*w[i]+b ] - a*v[i] = max( dp[ i-1 ][ t*w[i]+b ] - t*v[i]) ;
//我们的初始公式移项计算 dp[ i ][ a*w[i]+b ];
4.多重背包变式:能否构造问题
简单来说,就是给你n个物体,问在总代价小于等于 J 的情况下,可以构造出价值总和为1~K中的多少个值。
题目其实就是POJ 1742 coin
传送门:http://poj.org/problem?id=1742
这个问题其实相对于前面的问题反而还简单了。
我们构造一个队列,存放可转移元素。
如果数量不够,队首出队。
这样的话只要队列中有元素,就说明可以转移。
代码:
#include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> #include<cmath> #include<cstring> using namespace std; inline int gi() { int date = 0 , m = 1; char ch = 0; while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar(); if(ch == '-'){m = -1; ch = getchar();} while(ch<='9' && ch>='0'){ date = date * 10 + ch - '0'; ch = getchar(); }return date * m; } //特别说一下: //此题作为楼教主的男人八题之一,数据卡的非常紧 //只用单调队列优化到O(n^2)竟然还过不去 //必须要用下面的两个加速(01背包加速、乱搞加速)才跑的过去。 bool dp[100005]; int ans,n,m; int lk[100005]; int a[150],c[150]; void solve_coin() { for(int i = 1; i <= m ; i ++)dp[i] = 0; dp[0] = true; ans = 0; for(int i = 1; i <= n ; i ++) { if(c[i] == 1){ //只有一件:01背包问题加速 for(int s = m ; s >= a[i]; s -- ) if(!dp[s] && dp[s - a[i]])dp[s] = true; } else if(a[i]*c[i]>=m){ //数量多到乱搞都可以转移,加速 for(int s = a[i]; s <= m ; s ++) if(!dp[s] && dp[s - a[i]])dp[s] = true; } //单调队列优化部分: else for(int gg = 0 ; gg <= a[i] - 1; gg ++) //枚举余数 { int hd = 1,tl = 0; for(int s = 0; s*a[i] + gg <= m ; s ++) { while(hd<=tl && s - lk[hd]>c[i])hd++; //队首弹出不符合元素 if(dp[s*a[i]+gg])lk[++tl]=s; //入队 else if(hd<=tl)dp[s*a[i]+gg] = true; //转移 } } } return; } int main() { freopen("coin.in","r",stdin); while(1) { n = gi(); m = gi(); //n个物品,询问1~m if(n==0 && m==0)break; for(int i = 1; i <= n ; i ++)a[i] = gi(); //输入每个物体的价值 for(int i = 1; i <= n ; i ++)c[i] = gi(); //输入每个物体的数量上限 solve_coin(); for(int i = 1; i <= m ; i ++) if(dp[i])ans ++; printf("%d\n",ans); }return 0; }
5.尾言:
通过对多重背包的优化,大家应该可以看到单调队列的功效。
但在最后,我要说的是:单调队列有时候也可以优化DP!!
根据我自己的经验,一般可以这么做的DP有两个特点:
1> 涉及 取max 或者 取min
2> 通过我们的化简,可以将DP式左右两边化成同一形式。
不过单调队列对DP的优化,可就要根据具体题目来了,这也是我这里不赘述的原因。
希望大家能够自己去钻研这种题型,最后,希望我的一点见解能对大家有所帮助,谢谢观看。
相关文章推荐
- poj1742 Coins(多重背包+单调队列优化)
- POJ 1742 Coins 多重背包单调队列优化
- POJ 1742 Coins(多重背包 + 单调队列优化)
- POJ - 1742 Coins 多重背包+(二进制优化||单调队列优化)
- POJ 1742 Coins——不要套单调队列优化多重背包的模板
- poj 1742 多重背包 (单调队列优化)
- poj1742 单调队列优化多重背包
- POJ 1742:Coins——单调队列优化的多重背包
- POJ 1742 Coins 多重背包(单调队列优化)
- POJ 1742 Coins 多重背包用单调队列优化
- POJ 1742 Coins( 单调队列优化多重背包)
- POJ 1742 coins 多重背包单调队列优化
- 背包问题入门(单调队列优化多重背包
- POJ 1742 Coins 单调队列多重背包
- POJ1014:Dividing<动归,背包问题>
- POJ 3162 Walking Race 树的直径+单调队列(其实暴力也可以>_<)
- (poj 2823 Sliding Window)<单调队列裸题>
- poj1014多重背包--单调队列优化
- POJ 3260 The Fewest Coins(完全背包 + 多重背包 + 单调队列优化)
- poj1742 多重背包单调队列