您的位置:首页 > 其它

单调队列优化多重背包(含构造问题<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]) ;

观察左右式子,就会发现一个很神奇的事情:左右两边的式子是相等的!!!

先把代码贴出来:

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的优化,可就要根据具体题目来了,这也是我这里不赘述的原因。

希望大家能够自己去钻研这种题型,最后,希望我的一点见解能对大家有所帮助,谢谢观看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: