码迷,mamicode.com
首页 > 其他好文 > 详细

背包前三讲

时间:2019-10-19 14:59:06      阅读:72      评论:0      收藏:0      [点我收藏+]

标签:带来   无限   ros   www   编号   stream   max   二进制   mes   

背包问题是一类比较大的问题,问法也比较多,所以(dd大牛)大佬写了背包九讲,在这里我想先总结一下前三讲,一方面巩固自己,另一方面希望能给大家带来一定的帮助,愿与诸君共勉;

题目源自https://www.acwing.com/problem/可以对照这题目来看,如果新接触的话

01背包问题

给你n给物品,每个物品有体积和价值w,一个m体积的背包

在n个物品中选择,使得既不超容积,又价值最大

 1 #include <iostream>
 2 #include<bitset>
 3 using namespace std;
 4 const int  N=1010;
 5 int n,m;
 6 int f[N][N];//f[i][j]表示选到i物品,在使用体积为恰好j的情况下的价值最大值
 7 int main()
 8 {
 9     cin>>n>>m;
10     for(int i=1;i<=n;i++)
11     {
12         int v,w;
13         cin>>v>>w;
14         for(int j=0;j<=m;j++)
15         {
16             if(j-v>=0)
17                 f[i][j]=max(f[i-1][j],f[i-1][j-v]+w);//如果有可能装下当前物品,则进行一下比较
18             else
19                 f[i][j]=f[i-1][j];  //如果装不下当前物品,上一个状态也不能丢
20         }
21     }
22     int ma=0;
23     for(int i=0;i<=m;i++)//因为是使用体积恰好为j,所以还要进行一次遍历
24         ma=max(f[n][i],ma);
25     cout<<ma;
26     return 0;
27 }

01背包还可以进行空间上的优化

#include <iostream>
#include<bitset>
using namespace std;
const int  N=1010;
int n,m;
int f[N];//f[i]表示体积最大为i的时候的价值
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--)//因为每个物品只能用一次,所以反着枚举
        {
            f[j]=max(f[j-v]+w,f[j]);//j-v不可能小于0,所以不存在判断了
        }
    }
    cout<<f[m];
    return 0;
}

01背包到此结束

完全背包问题

背景同01背包,但是每个物品有无限多个

#include <iostream>
#include<bitset>
using namespace std;
const int  N=1010;
int n,m;
int f[N];//f[i]表示体积最大为i的时候的价值
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j=0;j<=m;j++)
        {
            if(j-v>=0)
                f[j]=max(f[j-v]+w,f[j]);
        }
    }
    cout<<f[m];
    return 0;
}

 可以发现只是将01背包的1维形式枚举体积时的顺序反过来了,同样的也可以用2维实现,具体的就不贴代码了

接下来是重头戏

多重背包问题

 背景同01背包,但是n代表物品种类数,每次给出物品种类时,会给出s代表该类物品的个数

那么想法很快就来了,把他拆成s个01背包不久得了么?

确实下面给出关键代码,不然都没人看了!qaq

for(int i=1;i<=n;i++)
    {
        int v,w,s;
        cin>>v>>w>>s;
        for(int j=m;j>=v;j--)//枚举体积
        {
            for(int k=0;k<=s;k++)//枚举决策
            {
                if(j-k*v>=0)
                    f[j]=max(f[j],f[j-k*v]+k*w);
            }
        }
    }

但是这样的时间复杂度有点过于高了

n*m*s,似乎只能解决100的问题,1000就1e9了,1s绝对TLE

于是就有了下面的方法(请继续往下看)

多重背包之二进制拆分

假设给你一个数,要你用尽可能少的数,能够组合成小于该数的所有的数

假设10

1 1 1 1 1 1 1 1 1 1 是可以的,也就是我们上面的方法

1 2 4 3 同样是可行的,明显下面一种更优

 

 1 #include <iostream>
 2 #include<bitset>
 3 #include<vector>
 4 using namespace std;
 5 const int  N=2010;
 6 int n,m;
 7 struct node
 8 {
 9     int v,w;
10 };
11 vector<node> ve;
12 int f[N];
13 int main()
14 {
15     cin>>n>>m;
16     for(int i=1;i<=n;i++)
17     {
18         int v,w,s;
19         cin>>v>>w>>s;
20         int t=1;
21         while(s>=t)
22         {
23             s-=t;
24             ve.push_back({t*v,t*w});
25             t*=2;
26         }
27         if(s)
28         ve.push_back({s*v,s*w});
29     }
30     //接下来就是01背包了
31     for(auto x:ve)//枚举物品
32     {
33         for(int j=m;j>=x.v;j--)//枚举体积
34         {
35             f[j]=max(f[j],f[j-x.v]+x.w);
36         }
37     }
38     cout<<f[m];
39     return 0;
40 }

 

时间复杂度为n*m*log(s),对付1000级别的问题没点问题,但是要是上10000了,就搞不定了

1e4*1e4*log(1e4)=3e9,所以我们得把log(s)得去掉,所以就有了下面的多重背包终极版

多重背包之单调队列优化

 

 

如果不会单调队列的可以去做一下滑动窗口这道题,就算你会单调队列,以我的水平我也不保证能让大佬们听懂qaq(请关爱萌新的我)

首先,单调队列是求区间最值的最快的方法,但是使用他的条件比较的苛刻,具体说来就是必须区间等长,而且询问从左至右

仔细观察多重背包转移的方程,可以发现对于某一个物品f[i]只能从f[i-v]转移过来,所以我们按余数将状态分组,所以就变成了求与f[i]%v相同的体积的最大值

但是此时滑动窗口的大小还没有确定,窗口大小k=min(m/v,s),意思就是当体积非常大的时候,你只能拿s个,所以此时窗口大小为s,而背包体积较小的时候,你只能装下m/v个,所以此时窗口大小为m/v

(其实当v*s大于m时,直接从头至尾枚举每个和j对v取余相同的体积,求出最大值,也是可行的,但是此时为了代码的统一,这样处理起来更加方便),如果看到这里不是很懂,可以看下代码的注释,语文差;

 1 #include <iostream>
 2 #include<bitset>
 3 #include<vector>
 4 using namespace std;
 5 const int  N=20100;
 6 int n,m;
 7 int f[N];
 8 struct node
 9 {
10     int id;
11     int val;
12 };
13 node qu[N];
14 int main()
15 {
16     cin>>n>>m;
17     for(int i=1;i<=n;i++)////枚举物品
18     {
19         int v,w,s;
20         cin>>v>>w>>s;
21         int k=min(s,m/v);//窗口大小
22         for(int mod=0;mod<v;mod++)//枚举余数
23         {
24             int tail=-1;
25             int head=0;
26             for(int l=0;l<=(m-mod)/v;l++)//枚举决策,从头到尾(%v余数相同)
27             {
28                 int x=l,y=f[l*v+mod]-l*w;//x为单调队列的编号,同时也是装多少个
29                                         //注意理解y,当枚举l+1时,因为空间都增长了一个v,那么价值至少会增加一个w
30                                         //而此时我们做的是选择出同组最大的价值,所以要保证在同一个起跑线上,所以要减去一个w
31                                         //这样说吧,就是你决策为l+1时,可能不是选的这个物品,价值大于w,但是你减去w之后,它和其他的组内成员在同一起跑线,而他更大,所以选他
32                 if(tail<head)
33                 {
34                     qu[++tail]={x,y};
35                 }
36                 else
37                 {
38                     while(tail>=head&&qu[head].id<l-k)head++;
39                     while(tail>=head&&qu[tail].val<=y)tail--;
40                     qu[++tail]={x,y};
41                 }
42                 f[l*v+mod]=qu[head].val+l*w;//f[j]等于额外的价值加上选当前物品的价值
43             }
44         }
45     }
46     cout<<f[m];
47     return 0;
48 }

在你懂单调队列的情况下,如果你能看懂那一大串注释的话,多重背包终极版你就掌握了,如果看不懂的话,请在心里暗道一句傻逼之后,去其他大佬的博客逛一逛,祝你弄懂这个。

 

背包前三讲

标签:带来   无限   ros   www   编号   stream   max   二进制   mes   

原文地址:https://www.cnblogs.com/greenofyu/p/11703582.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!